Merged /lucene/dev/trunk:r1523396-1523455

git-svn-id: https://svn.apache.org/repos/asf/lucene/dev/branches/lucene5207@1523456 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/dev-tools/idea/.idea/modules.xml b/dev-tools/idea/.idea/modules.xml
index 78e827f..58111cf 100644
--- a/dev-tools/idea/.idea/modules.xml
+++ b/dev-tools/idea/.idea/modules.xml
@@ -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" />
diff --git a/dev-tools/idea/lucene/expressions/expressions.iml b/dev-tools/idea/lucene/expressions/expressions.iml
new file mode 100644
index 0000000..7d9e4f0
--- /dev/null
+++ b/dev-tools/idea/lucene/expressions/expressions.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>
+
diff --git a/dev-tools/maven/lucene/expressions/pom.xml.template b/dev-tools/maven/lucene/expressions/pom.xml.template
new file mode 100644
index 0000000..4e1d525
--- /dev/null
+++ b/dev-tools/maven/lucene/expressions/pom.xml.template
@@ -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>
diff --git a/dev-tools/maven/lucene/pom.xml.template b/dev-tools/maven/lucene/pom.xml.template
index f4dc978..6709d3b 100644
--- a/dev-tools/maven/lucene/pom.xml.template
+++ b/dev-tools/maven/lucene/pom.xml.template
@@ -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>
diff --git a/dev-tools/maven/pom.xml.template b/dev-tools/maven/pom.xml.template
index f838045..451d723 100644
--- a/dev-tools/maven/pom.xml.template
+++ b/dev-tools/maven/pom.xml.template
@@ -266,6 +266,32 @@
         </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>
         <version>1.8.2</version>
diff --git a/dev-tools/scripts/smokeTestRelease.py b/dev-tools/scripts/smokeTestRelease.py
index a1b0f6f..f9328a1 100644
--- a/dev-tools/scripts/smokeTestRelease.py
+++ b/dev-tools/scripts/smokeTestRelease.py
@@ -651,7 +651,7 @@
 
   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:
diff --git a/lucene/build.xml b/lucene/build.xml
index 2034c63..0e50f6f 100644
--- a/lucene/build.xml
+++ b/lucene/build.xml
@@ -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 -->
diff --git a/lucene/common-build.xml b/lucene/common-build.xml
index f1c2c0f..9018652 100644
--- a/lucene/common-build.xml
+++ b/lucene/common-build.xml
@@ -1567,6 +1567,8 @@
         <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 -->
diff --git a/lucene/core/src/java/org/apache/lucene/util/MathUtil.java b/lucene/core/src/java/org/apache/lucene/util/MathUtil.java
index 9d40f4f..8b17600 100644
--- a/lucene/core/src/java/org/apache/lucene/util/MathUtil.java
+++ b/lucene/core/src/java/org/apache/lucene/util/MathUtil.java
@@ -44,6 +44,13 @@
     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 @@
     }
     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));
+  }
+
+
 }
diff --git a/lucene/core/src/test/org/apache/lucene/util/TestMathUtil.java b/lucene/core/src/test/org/apache/lucene/util/TestMathUtil.java
index ec1e2b9..f823aad 100644
--- a/lucene/core/src/test/org/apache/lucene/util/TestMathUtil.java
+++ b/lucene/core/src/test/org/apache/lucene/util/TestMathUtil.java
@@ -102,4 +102,80 @@
     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);
+  }
+
 }
diff --git a/lucene/expressions/build.xml b/lucene/expressions/build.xml
new file mode 100644
index 0000000..46a60fe
--- /dev/null
+++ b/lucene/expressions/build.xml
@@ -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>
diff --git a/lucene/expressions/ivy.xml b/lucene/expressions/ivy.xml
new file mode 100644
index 0000000..e48e517
--- /dev/null
+++ b/lucene/expressions/ivy.xml
@@ -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>
diff --git a/lucene/expressions/src/java/org/apache/lucene/expressions/Bindings.java b/lucene/expressions/src/java/org/apache/lucene/expressions/Bindings.java
new file mode 100644
index 0000000..5af1e5b
--- /dev/null
+++ b/lucene/expressions/src/java/org/apache/lucene/expressions/Bindings.java
@@ -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);
+  }
+}
diff --git a/lucene/expressions/src/java/org/apache/lucene/expressions/Expression.java b/lucene/expressions/src/java/org/apache/lucene/expressions/Expression.java
new file mode 100644
index 0000000..2d9a7f2
--- /dev/null
+++ b/lucene/expressions/src/java/org/apache/lucene/expressions/Expression.java
@@ -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);
+  }
+}
diff --git a/lucene/expressions/src/java/org/apache/lucene/expressions/ExpressionComparator.java b/lucene/expressions/src/java/org/apache/lucene/expressions/ExpressionComparator.java
new file mode 100644
index 0000000..fc5df4d
--- /dev/null
+++ b/lucene/expressions/src/java/org/apache/lucene/expressions/ExpressionComparator.java
@@ -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());
+  }
+}
diff --git a/lucene/expressions/src/java/org/apache/lucene/expressions/ExpressionFunctionValues.java b/lucene/expressions/src/java/org/apache/lucene/expressions/ExpressionFunctionValues.java
new file mode 100644
index 0000000..601c248
--- /dev/null
+++ b/lucene/expressions/src/java/org/apache/lucene/expressions/ExpressionFunctionValues.java
@@ -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) + ")";
+  }
+}
diff --git a/lucene/expressions/src/java/org/apache/lucene/expressions/ExpressionSortField.java b/lucene/expressions/src/java/org/apache/lucene/expressions/ExpressionSortField.java
new file mode 100644
index 0000000..4c301c8
--- /dev/null
+++ b/lucene/expressions/src/java/org/apache/lucene/expressions/ExpressionSortField.java
@@ -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);
+  }
+}
\ No newline at end of file
diff --git a/lucene/expressions/src/java/org/apache/lucene/expressions/ExpressionValueSource.java b/lucene/expressions/src/java/org/apache/lucene/expressions/ExpressionValueSource.java
new file mode 100644
index 0000000..425e039
--- /dev/null
+++ b/lucene/expressions/src/java/org/apache/lucene/expressions/ExpressionValueSource.java
@@ -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&lt;String,FunctionValues&gt;</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;
+  }
+}
diff --git a/lucene/expressions/src/java/org/apache/lucene/expressions/ScoreFunctionValues.java b/lucene/expressions/src/java/org/apache/lucene/expressions/ScoreFunctionValues.java
new file mode 100644
index 0000000..0c90d16
--- /dev/null
+++ b/lucene/expressions/src/java/org/apache/lucene/expressions/ScoreFunctionValues.java
@@ -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) + ")";
+  }
+}
diff --git a/lucene/expressions/src/java/org/apache/lucene/expressions/ScoreValueSource.java b/lucene/expressions/src/java/org/apache/lucene/expressions/ScoreValueSource.java
new file mode 100644
index 0000000..6fcee0f
--- /dev/null
+++ b/lucene/expressions/src/java/org/apache/lucene/expressions/ScoreValueSource.java
@@ -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";
+  }
+}
diff --git a/lucene/expressions/src/java/org/apache/lucene/expressions/SimpleBindings.java b/lucene/expressions/src/java/org/apache/lucene/expressions/SimpleBindings.java
new file mode 100644
index 0000000..64f811b
--- /dev/null
+++ b/lucene/expressions/src/java/org/apache/lucene/expressions/SimpleBindings.java
@@ -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();
+  }
+}
diff --git a/lucene/expressions/src/java/org/apache/lucene/expressions/js/Javascript.g b/lucene/expressions/src/java/org/apache/lucene/expressions/js/Javascript.g
new file mode 100644
index 0000000..e55e4e3
--- /dev/null
+++ b/lucene/expressions/src/java/org/apache/lucene/expressions/js/Javascript.g
@@ -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'
+    ;
diff --git a/lucene/expressions/src/java/org/apache/lucene/expressions/js/Javascript.tokens b/lucene/expressions/src/java/org/apache/lucene/expressions/js/Javascript.tokens
new file mode 100644
index 0000000..dc9a360
--- /dev/null
+++ b/lucene/expressions/src/java/org/apache/lucene/expressions/js/Javascript.tokens
@@ -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
diff --git a/lucene/expressions/src/java/org/apache/lucene/expressions/js/JavascriptCompiler.java b/lucene/expressions/src/java/org/apache/lucene/expressions/js/JavascriptCompiler.java
new file mode 100644
index 0000000..8c8bb53
--- /dev/null
+++ b/lucene/expressions/src/java/org/apache/lucene/expressions/js/JavascriptCompiler.java
@@ -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&lt;String,Method&gt; functions = new HashMap&lt;&gt;();
+ *   // 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.");
+    }
+  }
+}
diff --git a/lucene/expressions/src/java/org/apache/lucene/expressions/js/JavascriptLexer.java b/lucene/expressions/src/java/org/apache/lucene/expressions/js/JavascriptLexer.java
new file mode 100644
index 0000000..b747ff4
--- /dev/null
+++ b/lucene/expressions/src/java/org/apache/lucene/expressions/js/JavascriptLexer.java
@@ -0,0 +1,1833 @@
+// ANTLR GENERATED CODE: DO NOT EDIT
+
+package org.apache.lucene.expressions.js;
+
+import java.text.ParseException;
+
+
+import org.antlr.runtime.*;
+import java.util.Stack;
+import java.util.List;
+import java.util.ArrayList;
+
+@SuppressWarnings("all")
+class JavascriptLexer extends Lexer {
+  public static final int EOF=-1;
+  public static final int AT_ADD=4;
+  public static final int AT_BIT_AND=5;
+  public static final int AT_BIT_NOT=6;
+  public static final int AT_BIT_OR=7;
+  public static final int AT_BIT_SHL=8;
+  public static final int AT_BIT_SHR=9;
+  public static final int AT_BIT_SHU=10;
+  public static final int AT_BIT_XOR=11;
+  public static final int AT_BOOL_AND=12;
+  public static final int AT_BOOL_NOT=13;
+  public static final int AT_BOOL_OR=14;
+  public static final int AT_CALL=15;
+  public static final int AT_COLON=16;
+  public static final int AT_COMMA=17;
+  public static final int AT_COMP_EQ=18;
+  public static final int AT_COMP_GT=19;
+  public static final int AT_COMP_GTE=20;
+  public static final int AT_COMP_LT=21;
+  public static final int AT_COMP_LTE=22;
+  public static final int AT_COMP_NEQ=23;
+  public static final int AT_COND_QUE=24;
+  public static final int AT_DIVIDE=25;
+  public static final int AT_DOT=26;
+  public static final int AT_LPAREN=27;
+  public static final int AT_MODULO=28;
+  public static final int AT_MULTIPLY=29;
+  public static final int AT_NEGATE=30;
+  public static final int AT_RPAREN=31;
+  public static final int AT_SUBTRACT=32;
+  public static final int DECIMAL=33;
+  public static final int DECIMALDIGIT=34;
+  public static final int DECIMALINTEGER=35;
+  public static final int EXPONENT=36;
+  public static final int HEX=37;
+  public static final int HEXDIGIT=38;
+  public static final int ID=39;
+  public static final int OCTAL=40;
+  public static final int OCTALDIGIT=41;
+  public static final int WS=42;
+
+
+  @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);
+  }
+
+
+
+  // delegates
+  // delegators
+  public Lexer[] getDelegates() {
+    return new Lexer[] {};
+  }
+
+  public JavascriptLexer() {} 
+  public JavascriptLexer(CharStream input) {
+    this(input, new RecognizerSharedState());
+  }
+  public JavascriptLexer(CharStream input, RecognizerSharedState state) {
+    super(input,state);
+  }
+  @Override public String getGrammarFileName() { return "src/java/org/apache/lucene/expressions/js/Javascript.g"; }
+
+  // $ANTLR start "AT_ADD"
+  public final void mAT_ADD() throws RecognitionException {
+    try {
+      int _type = AT_ADD;
+      int _channel = DEFAULT_TOKEN_CHANNEL;
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:25:8: ( '+' )
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:25:10: '+'
+      {
+      match('+'); 
+      }
+
+      state.type = _type;
+      state.channel = _channel;
+    }
+    finally {
+      // do for sure before leaving
+    }
+  }
+  // $ANTLR end "AT_ADD"
+
+  // $ANTLR start "AT_BIT_AND"
+  public final void mAT_BIT_AND() throws RecognitionException {
+    try {
+      int _type = AT_BIT_AND;
+      int _channel = DEFAULT_TOKEN_CHANNEL;
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:26:12: ( '&' )
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:26:14: '&'
+      {
+      match('&'); 
+      }
+
+      state.type = _type;
+      state.channel = _channel;
+    }
+    finally {
+      // do for sure before leaving
+    }
+  }
+  // $ANTLR end "AT_BIT_AND"
+
+  // $ANTLR start "AT_BIT_NOT"
+  public final void mAT_BIT_NOT() throws RecognitionException {
+    try {
+      int _type = AT_BIT_NOT;
+      int _channel = DEFAULT_TOKEN_CHANNEL;
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:27:12: ( '~' )
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:27:14: '~'
+      {
+      match('~'); 
+      }
+
+      state.type = _type;
+      state.channel = _channel;
+    }
+    finally {
+      // do for sure before leaving
+    }
+  }
+  // $ANTLR end "AT_BIT_NOT"
+
+  // $ANTLR start "AT_BIT_OR"
+  public final void mAT_BIT_OR() throws RecognitionException {
+    try {
+      int _type = AT_BIT_OR;
+      int _channel = DEFAULT_TOKEN_CHANNEL;
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:28:11: ( '|' )
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:28:13: '|'
+      {
+      match('|'); 
+      }
+
+      state.type = _type;
+      state.channel = _channel;
+    }
+    finally {
+      // do for sure before leaving
+    }
+  }
+  // $ANTLR end "AT_BIT_OR"
+
+  // $ANTLR start "AT_BIT_SHL"
+  public final void mAT_BIT_SHL() throws RecognitionException {
+    try {
+      int _type = AT_BIT_SHL;
+      int _channel = DEFAULT_TOKEN_CHANNEL;
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:29:12: ( '<<' )
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:29:14: '<<'
+      {
+      match("<<"); 
+
+      }
+
+      state.type = _type;
+      state.channel = _channel;
+    }
+    finally {
+      // do for sure before leaving
+    }
+  }
+  // $ANTLR end "AT_BIT_SHL"
+
+  // $ANTLR start "AT_BIT_SHR"
+  public final void mAT_BIT_SHR() throws RecognitionException {
+    try {
+      int _type = AT_BIT_SHR;
+      int _channel = DEFAULT_TOKEN_CHANNEL;
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:30:12: ( '>>' )
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:30:14: '>>'
+      {
+      match(">>"); 
+
+      }
+
+      state.type = _type;
+      state.channel = _channel;
+    }
+    finally {
+      // do for sure before leaving
+    }
+  }
+  // $ANTLR end "AT_BIT_SHR"
+
+  // $ANTLR start "AT_BIT_SHU"
+  public final void mAT_BIT_SHU() throws RecognitionException {
+    try {
+      int _type = AT_BIT_SHU;
+      int _channel = DEFAULT_TOKEN_CHANNEL;
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:31:12: ( '>>>' )
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:31:14: '>>>'
+      {
+      match(">>>"); 
+
+      }
+
+      state.type = _type;
+      state.channel = _channel;
+    }
+    finally {
+      // do for sure before leaving
+    }
+  }
+  // $ANTLR end "AT_BIT_SHU"
+
+  // $ANTLR start "AT_BIT_XOR"
+  public final void mAT_BIT_XOR() throws RecognitionException {
+    try {
+      int _type = AT_BIT_XOR;
+      int _channel = DEFAULT_TOKEN_CHANNEL;
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:32:12: ( '^' )
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:32:14: '^'
+      {
+      match('^'); 
+      }
+
+      state.type = _type;
+      state.channel = _channel;
+    }
+    finally {
+      // do for sure before leaving
+    }
+  }
+  // $ANTLR end "AT_BIT_XOR"
+
+  // $ANTLR start "AT_BOOL_AND"
+  public final void mAT_BOOL_AND() throws RecognitionException {
+    try {
+      int _type = AT_BOOL_AND;
+      int _channel = DEFAULT_TOKEN_CHANNEL;
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:33:13: ( '&&' )
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:33:15: '&&'
+      {
+      match("&&"); 
+
+      }
+
+      state.type = _type;
+      state.channel = _channel;
+    }
+    finally {
+      // do for sure before leaving
+    }
+  }
+  // $ANTLR end "AT_BOOL_AND"
+
+  // $ANTLR start "AT_BOOL_NOT"
+  public final void mAT_BOOL_NOT() throws RecognitionException {
+    try {
+      int _type = AT_BOOL_NOT;
+      int _channel = DEFAULT_TOKEN_CHANNEL;
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:34:13: ( '!' )
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:34:15: '!'
+      {
+      match('!'); 
+      }
+
+      state.type = _type;
+      state.channel = _channel;
+    }
+    finally {
+      // do for sure before leaving
+    }
+  }
+  // $ANTLR end "AT_BOOL_NOT"
+
+  // $ANTLR start "AT_BOOL_OR"
+  public final void mAT_BOOL_OR() throws RecognitionException {
+    try {
+      int _type = AT_BOOL_OR;
+      int _channel = DEFAULT_TOKEN_CHANNEL;
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:35:12: ( '||' )
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:35:14: '||'
+      {
+      match("||"); 
+
+      }
+
+      state.type = _type;
+      state.channel = _channel;
+    }
+    finally {
+      // do for sure before leaving
+    }
+  }
+  // $ANTLR end "AT_BOOL_OR"
+
+  // $ANTLR start "AT_COLON"
+  public final void mAT_COLON() throws RecognitionException {
+    try {
+      int _type = AT_COLON;
+      int _channel = DEFAULT_TOKEN_CHANNEL;
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:36:10: ( ':' )
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:36:12: ':'
+      {
+      match(':'); 
+      }
+
+      state.type = _type;
+      state.channel = _channel;
+    }
+    finally {
+      // do for sure before leaving
+    }
+  }
+  // $ANTLR end "AT_COLON"
+
+  // $ANTLR start "AT_COMMA"
+  public final void mAT_COMMA() throws RecognitionException {
+    try {
+      int _type = AT_COMMA;
+      int _channel = DEFAULT_TOKEN_CHANNEL;
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:37:10: ( ',' )
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:37:12: ','
+      {
+      match(','); 
+      }
+
+      state.type = _type;
+      state.channel = _channel;
+    }
+    finally {
+      // do for sure before leaving
+    }
+  }
+  // $ANTLR end "AT_COMMA"
+
+  // $ANTLR start "AT_COMP_EQ"
+  public final void mAT_COMP_EQ() throws RecognitionException {
+    try {
+      int _type = AT_COMP_EQ;
+      int _channel = DEFAULT_TOKEN_CHANNEL;
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:38:12: ( '==' )
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:38:14: '=='
+      {
+      match("=="); 
+
+      }
+
+      state.type = _type;
+      state.channel = _channel;
+    }
+    finally {
+      // do for sure before leaving
+    }
+  }
+  // $ANTLR end "AT_COMP_EQ"
+
+  // $ANTLR start "AT_COMP_GT"
+  public final void mAT_COMP_GT() throws RecognitionException {
+    try {
+      int _type = AT_COMP_GT;
+      int _channel = DEFAULT_TOKEN_CHANNEL;
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:39:12: ( '>' )
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:39:14: '>'
+      {
+      match('>'); 
+      }
+
+      state.type = _type;
+      state.channel = _channel;
+    }
+    finally {
+      // do for sure before leaving
+    }
+  }
+  // $ANTLR end "AT_COMP_GT"
+
+  // $ANTLR start "AT_COMP_GTE"
+  public final void mAT_COMP_GTE() throws RecognitionException {
+    try {
+      int _type = AT_COMP_GTE;
+      int _channel = DEFAULT_TOKEN_CHANNEL;
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:40:13: ( '>=' )
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:40:15: '>='
+      {
+      match(">="); 
+
+      }
+
+      state.type = _type;
+      state.channel = _channel;
+    }
+    finally {
+      // do for sure before leaving
+    }
+  }
+  // $ANTLR end "AT_COMP_GTE"
+
+  // $ANTLR start "AT_COMP_LT"
+  public final void mAT_COMP_LT() throws RecognitionException {
+    try {
+      int _type = AT_COMP_LT;
+      int _channel = DEFAULT_TOKEN_CHANNEL;
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:41:12: ( '<' )
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:41:14: '<'
+      {
+      match('<'); 
+      }
+
+      state.type = _type;
+      state.channel = _channel;
+    }
+    finally {
+      // do for sure before leaving
+    }
+  }
+  // $ANTLR end "AT_COMP_LT"
+
+  // $ANTLR start "AT_COMP_LTE"
+  public final void mAT_COMP_LTE() throws RecognitionException {
+    try {
+      int _type = AT_COMP_LTE;
+      int _channel = DEFAULT_TOKEN_CHANNEL;
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:42:13: ( '<=' )
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:42:15: '<='
+      {
+      match("<="); 
+
+      }
+
+      state.type = _type;
+      state.channel = _channel;
+    }
+    finally {
+      // do for sure before leaving
+    }
+  }
+  // $ANTLR end "AT_COMP_LTE"
+
+  // $ANTLR start "AT_COMP_NEQ"
+  public final void mAT_COMP_NEQ() throws RecognitionException {
+    try {
+      int _type = AT_COMP_NEQ;
+      int _channel = DEFAULT_TOKEN_CHANNEL;
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:43:13: ( '!=' )
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:43:15: '!='
+      {
+      match("!="); 
+
+      }
+
+      state.type = _type;
+      state.channel = _channel;
+    }
+    finally {
+      // do for sure before leaving
+    }
+  }
+  // $ANTLR end "AT_COMP_NEQ"
+
+  // $ANTLR start "AT_COND_QUE"
+  public final void mAT_COND_QUE() throws RecognitionException {
+    try {
+      int _type = AT_COND_QUE;
+      int _channel = DEFAULT_TOKEN_CHANNEL;
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:44:13: ( '?' )
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:44:15: '?'
+      {
+      match('?'); 
+      }
+
+      state.type = _type;
+      state.channel = _channel;
+    }
+    finally {
+      // do for sure before leaving
+    }
+  }
+  // $ANTLR end "AT_COND_QUE"
+
+  // $ANTLR start "AT_DIVIDE"
+  public final void mAT_DIVIDE() throws RecognitionException {
+    try {
+      int _type = AT_DIVIDE;
+      int _channel = DEFAULT_TOKEN_CHANNEL;
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:45:11: ( '/' )
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:45:13: '/'
+      {
+      match('/'); 
+      }
+
+      state.type = _type;
+      state.channel = _channel;
+    }
+    finally {
+      // do for sure before leaving
+    }
+  }
+  // $ANTLR end "AT_DIVIDE"
+
+  // $ANTLR start "AT_DOT"
+  public final void mAT_DOT() throws RecognitionException {
+    try {
+      int _type = AT_DOT;
+      int _channel = DEFAULT_TOKEN_CHANNEL;
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:46:8: ( '.' )
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:46:10: '.'
+      {
+      match('.'); 
+      }
+
+      state.type = _type;
+      state.channel = _channel;
+    }
+    finally {
+      // do for sure before leaving
+    }
+  }
+  // $ANTLR end "AT_DOT"
+
+  // $ANTLR start "AT_LPAREN"
+  public final void mAT_LPAREN() throws RecognitionException {
+    try {
+      int _type = AT_LPAREN;
+      int _channel = DEFAULT_TOKEN_CHANNEL;
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:47:11: ( '(' )
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:47:13: '('
+      {
+      match('('); 
+      }
+
+      state.type = _type;
+      state.channel = _channel;
+    }
+    finally {
+      // do for sure before leaving
+    }
+  }
+  // $ANTLR end "AT_LPAREN"
+
+  // $ANTLR start "AT_MODULO"
+  public final void mAT_MODULO() throws RecognitionException {
+    try {
+      int _type = AT_MODULO;
+      int _channel = DEFAULT_TOKEN_CHANNEL;
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:48:11: ( '%' )
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:48:13: '%'
+      {
+      match('%'); 
+      }
+
+      state.type = _type;
+      state.channel = _channel;
+    }
+    finally {
+      // do for sure before leaving
+    }
+  }
+  // $ANTLR end "AT_MODULO"
+
+  // $ANTLR start "AT_MULTIPLY"
+  public final void mAT_MULTIPLY() throws RecognitionException {
+    try {
+      int _type = AT_MULTIPLY;
+      int _channel = DEFAULT_TOKEN_CHANNEL;
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:49:13: ( '*' )
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:49:15: '*'
+      {
+      match('*'); 
+      }
+
+      state.type = _type;
+      state.channel = _channel;
+    }
+    finally {
+      // do for sure before leaving
+    }
+  }
+  // $ANTLR end "AT_MULTIPLY"
+
+  // $ANTLR start "AT_RPAREN"
+  public final void mAT_RPAREN() throws RecognitionException {
+    try {
+      int _type = AT_RPAREN;
+      int _channel = DEFAULT_TOKEN_CHANNEL;
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:50:11: ( ')' )
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:50:13: ')'
+      {
+      match(')'); 
+      }
+
+      state.type = _type;
+      state.channel = _channel;
+    }
+    finally {
+      // do for sure before leaving
+    }
+  }
+  // $ANTLR end "AT_RPAREN"
+
+  // $ANTLR start "AT_SUBTRACT"
+  public final void mAT_SUBTRACT() throws RecognitionException {
+    try {
+      int _type = AT_SUBTRACT;
+      int _channel = DEFAULT_TOKEN_CHANNEL;
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:51:13: ( '-' )
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:51:15: '-'
+      {
+      match('-'); 
+      }
+
+      state.type = _type;
+      state.channel = _channel;
+    }
+    finally {
+      // do for sure before leaving
+    }
+  }
+  // $ANTLR end "AT_SUBTRACT"
+
+  // $ANTLR start "ID"
+  public final void mID() throws RecognitionException {
+    try {
+      int _type = ID;
+      int _channel = DEFAULT_TOKEN_CHANNEL;
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:334:5: ( ( 'a' .. 'z' | 'A' .. 'Z' | '_' ) ( 'a' .. 'z' | 'A' .. 'Z' | '0' .. '9' | '_' )* )
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:334:7: ( 'a' .. 'z' | 'A' .. 'Z' | '_' ) ( 'a' .. 'z' | 'A' .. 'Z' | '0' .. '9' | '_' )*
+      {
+      if ( (input.LA(1) >= 'A' && input.LA(1) <= 'Z')||input.LA(1)=='_'||(input.LA(1) >= 'a' && input.LA(1) <= 'z') ) {
+        input.consume();
+      }
+      else {
+        MismatchedSetException mse = new MismatchedSetException(null,input);
+        recover(mse);
+        throw mse;
+      }
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:334:31: ( 'a' .. 'z' | 'A' .. 'Z' | '0' .. '9' | '_' )*
+      loop1:
+      while (true) {
+        int alt1=2;
+        int LA1_0 = input.LA(1);
+        if ( ((LA1_0 >= '0' && LA1_0 <= '9')||(LA1_0 >= 'A' && LA1_0 <= 'Z')||LA1_0=='_'||(LA1_0 >= 'a' && LA1_0 <= 'z')) ) {
+          alt1=1;
+        }
+
+        switch (alt1) {
+        case 1 :
+          // src/java/org/apache/lucene/expressions/js/Javascript.g:
+          {
+          if ( (input.LA(1) >= '0' && input.LA(1) <= '9')||(input.LA(1) >= 'A' && input.LA(1) <= 'Z')||input.LA(1)=='_'||(input.LA(1) >= 'a' && input.LA(1) <= 'z') ) {
+            input.consume();
+          }
+          else {
+            MismatchedSetException mse = new MismatchedSetException(null,input);
+            recover(mse);
+            throw mse;
+          }
+          }
+          break;
+
+        default :
+          break loop1;
+        }
+      }
+
+      }
+
+      state.type = _type;
+      state.channel = _channel;
+    }
+    finally {
+      // do for sure before leaving
+    }
+  }
+  // $ANTLR end "ID"
+
+  // $ANTLR start "WS"
+  public final void mWS() throws RecognitionException {
+    try {
+      int _type = WS;
+      int _channel = DEFAULT_TOKEN_CHANNEL;
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:338:5: ( ( ' ' | '\\t' | '\\n' | '\\r' )+ )
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:338:7: ( ' ' | '\\t' | '\\n' | '\\r' )+
+      {
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:338:7: ( ' ' | '\\t' | '\\n' | '\\r' )+
+      int cnt2=0;
+      loop2:
+      while (true) {
+        int alt2=2;
+        int LA2_0 = input.LA(1);
+        if ( ((LA2_0 >= '\t' && LA2_0 <= '\n')||LA2_0=='\r'||LA2_0==' ') ) {
+          alt2=1;
+        }
+
+        switch (alt2) {
+        case 1 :
+          // src/java/org/apache/lucene/expressions/js/Javascript.g:
+          {
+          if ( (input.LA(1) >= '\t' && input.LA(1) <= '\n')||input.LA(1)=='\r'||input.LA(1)==' ' ) {
+            input.consume();
+          }
+          else {
+            MismatchedSetException mse = new MismatchedSetException(null,input);
+            recover(mse);
+            throw mse;
+          }
+          }
+          break;
+
+        default :
+          if ( cnt2 >= 1 ) break loop2;
+          EarlyExitException eee = new EarlyExitException(2, input);
+          throw eee;
+        }
+        cnt2++;
+      }
+
+      skip();
+      }
+
+      state.type = _type;
+      state.channel = _channel;
+    }
+    finally {
+      // do for sure before leaving
+    }
+  }
+  // $ANTLR end "WS"
+
+  // $ANTLR start "DECIMAL"
+  public final void mDECIMAL() throws RecognitionException {
+    try {
+      int _type = DECIMAL;
+      int _channel = DEFAULT_TOKEN_CHANNEL;
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:342:5: ( DECIMALINTEGER AT_DOT ( DECIMALDIGIT )* ( EXPONENT )? | AT_DOT ( DECIMALDIGIT )+ ( EXPONENT )? | DECIMALINTEGER ( EXPONENT )? )
+      int alt8=3;
+      alt8 = dfa8.predict(input);
+      switch (alt8) {
+        case 1 :
+          // src/java/org/apache/lucene/expressions/js/Javascript.g:342:7: DECIMALINTEGER AT_DOT ( DECIMALDIGIT )* ( EXPONENT )?
+          {
+          mDECIMALINTEGER(); 
+
+          mAT_DOT(); 
+
+          // src/java/org/apache/lucene/expressions/js/Javascript.g:342:29: ( DECIMALDIGIT )*
+          loop3:
+          while (true) {
+            int alt3=2;
+            int LA3_0 = input.LA(1);
+            if ( ((LA3_0 >= '0' && LA3_0 <= '9')) ) {
+              alt3=1;
+            }
+
+            switch (alt3) {
+            case 1 :
+              // src/java/org/apache/lucene/expressions/js/Javascript.g:
+              {
+              if ( (input.LA(1) >= '0' && input.LA(1) <= '9') ) {
+                input.consume();
+              }
+              else {
+                MismatchedSetException mse = new MismatchedSetException(null,input);
+                recover(mse);
+                throw mse;
+              }
+              }
+              break;
+
+            default :
+              break loop3;
+            }
+          }
+
+          // src/java/org/apache/lucene/expressions/js/Javascript.g:342:43: ( EXPONENT )?
+          int alt4=2;
+          int LA4_0 = input.LA(1);
+          if ( (LA4_0=='E'||LA4_0=='e') ) {
+            alt4=1;
+          }
+          switch (alt4) {
+            case 1 :
+              // src/java/org/apache/lucene/expressions/js/Javascript.g:342:43: EXPONENT
+              {
+              mEXPONENT(); 
+
+              }
+              break;
+
+          }
+
+          }
+          break;
+        case 2 :
+          // src/java/org/apache/lucene/expressions/js/Javascript.g:343:7: AT_DOT ( DECIMALDIGIT )+ ( EXPONENT )?
+          {
+          mAT_DOT(); 
+
+          // src/java/org/apache/lucene/expressions/js/Javascript.g:343:14: ( DECIMALDIGIT )+
+          int cnt5=0;
+          loop5:
+          while (true) {
+            int alt5=2;
+            int LA5_0 = input.LA(1);
+            if ( ((LA5_0 >= '0' && LA5_0 <= '9')) ) {
+              alt5=1;
+            }
+
+            switch (alt5) {
+            case 1 :
+              // src/java/org/apache/lucene/expressions/js/Javascript.g:
+              {
+              if ( (input.LA(1) >= '0' && input.LA(1) <= '9') ) {
+                input.consume();
+              }
+              else {
+                MismatchedSetException mse = new MismatchedSetException(null,input);
+                recover(mse);
+                throw mse;
+              }
+              }
+              break;
+
+            default :
+              if ( cnt5 >= 1 ) break loop5;
+              EarlyExitException eee = new EarlyExitException(5, input);
+              throw eee;
+            }
+            cnt5++;
+          }
+
+          // src/java/org/apache/lucene/expressions/js/Javascript.g:343:28: ( EXPONENT )?
+          int alt6=2;
+          int LA6_0 = input.LA(1);
+          if ( (LA6_0=='E'||LA6_0=='e') ) {
+            alt6=1;
+          }
+          switch (alt6) {
+            case 1 :
+              // src/java/org/apache/lucene/expressions/js/Javascript.g:343:28: EXPONENT
+              {
+              mEXPONENT(); 
+
+              }
+              break;
+
+          }
+
+          }
+          break;
+        case 3 :
+          // src/java/org/apache/lucene/expressions/js/Javascript.g:344:7: DECIMALINTEGER ( EXPONENT )?
+          {
+          mDECIMALINTEGER(); 
+
+          // src/java/org/apache/lucene/expressions/js/Javascript.g:344:22: ( EXPONENT )?
+          int alt7=2;
+          int LA7_0 = input.LA(1);
+          if ( (LA7_0=='E'||LA7_0=='e') ) {
+            alt7=1;
+          }
+          switch (alt7) {
+            case 1 :
+              // src/java/org/apache/lucene/expressions/js/Javascript.g:344:22: EXPONENT
+              {
+              mEXPONENT(); 
+
+              }
+              break;
+
+          }
+
+          }
+          break;
+
+      }
+      state.type = _type;
+      state.channel = _channel;
+    }
+    finally {
+      // do for sure before leaving
+    }
+  }
+  // $ANTLR end "DECIMAL"
+
+  // $ANTLR start "OCTAL"
+  public final void mOCTAL() throws RecognitionException {
+    try {
+      int _type = OCTAL;
+      int _channel = DEFAULT_TOKEN_CHANNEL;
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:348:5: ( '0' ( OCTALDIGIT )+ )
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:348:7: '0' ( OCTALDIGIT )+
+      {
+      match('0'); 
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:348:11: ( OCTALDIGIT )+
+      int cnt9=0;
+      loop9:
+      while (true) {
+        int alt9=2;
+        int LA9_0 = input.LA(1);
+        if ( ((LA9_0 >= '0' && LA9_0 <= '7')) ) {
+          alt9=1;
+        }
+
+        switch (alt9) {
+        case 1 :
+          // src/java/org/apache/lucene/expressions/js/Javascript.g:
+          {
+          if ( (input.LA(1) >= '0' && input.LA(1) <= '7') ) {
+            input.consume();
+          }
+          else {
+            MismatchedSetException mse = new MismatchedSetException(null,input);
+            recover(mse);
+            throw mse;
+          }
+          }
+          break;
+
+        default :
+          if ( cnt9 >= 1 ) break loop9;
+          EarlyExitException eee = new EarlyExitException(9, input);
+          throw eee;
+        }
+        cnt9++;
+      }
+
+      }
+
+      state.type = _type;
+      state.channel = _channel;
+    }
+    finally {
+      // do for sure before leaving
+    }
+  }
+  // $ANTLR end "OCTAL"
+
+  // $ANTLR start "HEX"
+  public final void mHEX() throws RecognitionException {
+    try {
+      int _type = HEX;
+      int _channel = DEFAULT_TOKEN_CHANNEL;
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:352:5: ( ( '0x' | '0X' ) ( HEXDIGIT )+ )
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:352:7: ( '0x' | '0X' ) ( HEXDIGIT )+
+      {
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:352:7: ( '0x' | '0X' )
+      int alt10=2;
+      int LA10_0 = input.LA(1);
+      if ( (LA10_0=='0') ) {
+        int LA10_1 = input.LA(2);
+        if ( (LA10_1=='x') ) {
+          alt10=1;
+        }
+        else if ( (LA10_1=='X') ) {
+          alt10=2;
+        }
+
+        else {
+          int nvaeMark = input.mark();
+          try {
+            input.consume();
+            NoViableAltException nvae =
+              new NoViableAltException("", 10, 1, input);
+            throw nvae;
+          } finally {
+            input.rewind(nvaeMark);
+          }
+        }
+
+      }
+
+      else {
+        NoViableAltException nvae =
+          new NoViableAltException("", 10, 0, input);
+        throw nvae;
+      }
+
+      switch (alt10) {
+        case 1 :
+          // src/java/org/apache/lucene/expressions/js/Javascript.g:352:8: '0x'
+          {
+          match("0x"); 
+
+          }
+          break;
+        case 2 :
+          // src/java/org/apache/lucene/expressions/js/Javascript.g:352:13: '0X'
+          {
+          match("0X"); 
+
+          }
+          break;
+
+      }
+
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:352:19: ( HEXDIGIT )+
+      int cnt11=0;
+      loop11:
+      while (true) {
+        int alt11=2;
+        int LA11_0 = input.LA(1);
+        if ( ((LA11_0 >= '0' && LA11_0 <= '9')||(LA11_0 >= 'A' && LA11_0 <= 'F')||(LA11_0 >= 'a' && LA11_0 <= 'f')) ) {
+          alt11=1;
+        }
+
+        switch (alt11) {
+        case 1 :
+          // src/java/org/apache/lucene/expressions/js/Javascript.g:
+          {
+          if ( (input.LA(1) >= '0' && input.LA(1) <= '9')||(input.LA(1) >= 'A' && input.LA(1) <= 'F')||(input.LA(1) >= 'a' && input.LA(1) <= 'f') ) {
+            input.consume();
+          }
+          else {
+            MismatchedSetException mse = new MismatchedSetException(null,input);
+            recover(mse);
+            throw mse;
+          }
+          }
+          break;
+
+        default :
+          if ( cnt11 >= 1 ) break loop11;
+          EarlyExitException eee = new EarlyExitException(11, input);
+          throw eee;
+        }
+        cnt11++;
+      }
+
+      }
+
+      state.type = _type;
+      state.channel = _channel;
+    }
+    finally {
+      // do for sure before leaving
+    }
+  }
+  // $ANTLR end "HEX"
+
+  // $ANTLR start "DECIMALINTEGER"
+  public final void mDECIMALINTEGER() throws RecognitionException {
+    try {
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:358:5: ( '0' | '1' .. '9' ( DECIMALDIGIT )* )
+      int alt13=2;
+      int LA13_0 = input.LA(1);
+      if ( (LA13_0=='0') ) {
+        alt13=1;
+      }
+      else if ( ((LA13_0 >= '1' && LA13_0 <= '9')) ) {
+        alt13=2;
+      }
+
+      else {
+        NoViableAltException nvae =
+          new NoViableAltException("", 13, 0, input);
+        throw nvae;
+      }
+
+      switch (alt13) {
+        case 1 :
+          // src/java/org/apache/lucene/expressions/js/Javascript.g:358:7: '0'
+          {
+          match('0'); 
+          }
+          break;
+        case 2 :
+          // src/java/org/apache/lucene/expressions/js/Javascript.g:359:7: '1' .. '9' ( DECIMALDIGIT )*
+          {
+          matchRange('1','9'); 
+          // src/java/org/apache/lucene/expressions/js/Javascript.g:359:16: ( DECIMALDIGIT )*
+          loop12:
+          while (true) {
+            int alt12=2;
+            int LA12_0 = input.LA(1);
+            if ( ((LA12_0 >= '0' && LA12_0 <= '9')) ) {
+              alt12=1;
+            }
+
+            switch (alt12) {
+            case 1 :
+              // src/java/org/apache/lucene/expressions/js/Javascript.g:
+              {
+              if ( (input.LA(1) >= '0' && input.LA(1) <= '9') ) {
+                input.consume();
+              }
+              else {
+                MismatchedSetException mse = new MismatchedSetException(null,input);
+                recover(mse);
+                throw mse;
+              }
+              }
+              break;
+
+            default :
+              break loop12;
+            }
+          }
+
+          }
+          break;
+
+      }
+    }
+    finally {
+      // do for sure before leaving
+    }
+  }
+  // $ANTLR end "DECIMALINTEGER"
+
+  // $ANTLR start "EXPONENT"
+  public final void mEXPONENT() throws RecognitionException {
+    try {
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:364:5: ( ( 'e' | 'E' ) ( '+' | '-' )? ( DECIMALDIGIT )+ )
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:364:7: ( 'e' | 'E' ) ( '+' | '-' )? ( DECIMALDIGIT )+
+      {
+      if ( input.LA(1)=='E'||input.LA(1)=='e' ) {
+        input.consume();
+      }
+      else {
+        MismatchedSetException mse = new MismatchedSetException(null,input);
+        recover(mse);
+        throw mse;
+      }
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:364:17: ( '+' | '-' )?
+      int alt14=2;
+      int LA14_0 = input.LA(1);
+      if ( (LA14_0=='+'||LA14_0=='-') ) {
+        alt14=1;
+      }
+      switch (alt14) {
+        case 1 :
+          // src/java/org/apache/lucene/expressions/js/Javascript.g:
+          {
+          if ( input.LA(1)=='+'||input.LA(1)=='-' ) {
+            input.consume();
+          }
+          else {
+            MismatchedSetException mse = new MismatchedSetException(null,input);
+            recover(mse);
+            throw mse;
+          }
+          }
+          break;
+
+      }
+
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:364:28: ( DECIMALDIGIT )+
+      int cnt15=0;
+      loop15:
+      while (true) {
+        int alt15=2;
+        int LA15_0 = input.LA(1);
+        if ( ((LA15_0 >= '0' && LA15_0 <= '9')) ) {
+          alt15=1;
+        }
+
+        switch (alt15) {
+        case 1 :
+          // src/java/org/apache/lucene/expressions/js/Javascript.g:
+          {
+          if ( (input.LA(1) >= '0' && input.LA(1) <= '9') ) {
+            input.consume();
+          }
+          else {
+            MismatchedSetException mse = new MismatchedSetException(null,input);
+            recover(mse);
+            throw mse;
+          }
+          }
+          break;
+
+        default :
+          if ( cnt15 >= 1 ) break loop15;
+          EarlyExitException eee = new EarlyExitException(15, input);
+          throw eee;
+        }
+        cnt15++;
+      }
+
+      }
+
+    }
+    finally {
+      // do for sure before leaving
+    }
+  }
+  // $ANTLR end "EXPONENT"
+
+  // $ANTLR start "DECIMALDIGIT"
+  public final void mDECIMALDIGIT() throws RecognitionException {
+    try {
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:369:5: ( '0' .. '9' )
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:
+      {
+      if ( (input.LA(1) >= '0' && input.LA(1) <= '9') ) {
+        input.consume();
+      }
+      else {
+        MismatchedSetException mse = new MismatchedSetException(null,input);
+        recover(mse);
+        throw mse;
+      }
+      }
+
+    }
+    finally {
+      // do for sure before leaving
+    }
+  }
+  // $ANTLR end "DECIMALDIGIT"
+
+  // $ANTLR start "HEXDIGIT"
+  public final void mHEXDIGIT() throws RecognitionException {
+    try {
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:374:5: ( DECIMALDIGIT | 'a' .. 'f' | 'A' .. 'F' )
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:
+      {
+      if ( (input.LA(1) >= '0' && input.LA(1) <= '9')||(input.LA(1) >= 'A' && input.LA(1) <= 'F')||(input.LA(1) >= 'a' && input.LA(1) <= 'f') ) {
+        input.consume();
+      }
+      else {
+        MismatchedSetException mse = new MismatchedSetException(null,input);
+        recover(mse);
+        throw mse;
+      }
+      }
+
+    }
+    finally {
+      // do for sure before leaving
+    }
+  }
+  // $ANTLR end "HEXDIGIT"
+
+  // $ANTLR start "OCTALDIGIT"
+  public final void mOCTALDIGIT() throws RecognitionException {
+    try {
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:381:5: ( '0' .. '7' )
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:
+      {
+      if ( (input.LA(1) >= '0' && input.LA(1) <= '7') ) {
+        input.consume();
+      }
+      else {
+        MismatchedSetException mse = new MismatchedSetException(null,input);
+        recover(mse);
+        throw mse;
+      }
+      }
+
+    }
+    finally {
+      // do for sure before leaving
+    }
+  }
+  // $ANTLR end "OCTALDIGIT"
+
+  @Override
+  public void mTokens() throws RecognitionException {
+    // src/java/org/apache/lucene/expressions/js/Javascript.g:1:8: ( AT_ADD | AT_BIT_AND | AT_BIT_NOT | AT_BIT_OR | AT_BIT_SHL | AT_BIT_SHR | AT_BIT_SHU | AT_BIT_XOR | AT_BOOL_AND | AT_BOOL_NOT | AT_BOOL_OR | AT_COLON | AT_COMMA | AT_COMP_EQ | AT_COMP_GT | AT_COMP_GTE | AT_COMP_LT | AT_COMP_LTE | AT_COMP_NEQ | AT_COND_QUE | AT_DIVIDE | AT_DOT | AT_LPAREN | AT_MODULO | AT_MULTIPLY | AT_RPAREN | AT_SUBTRACT | ID | WS | DECIMAL | OCTAL | HEX )
+    int alt16=32;
+    switch ( input.LA(1) ) {
+    case '+':
+      {
+      alt16=1;
+      }
+      break;
+    case '&':
+      {
+      int LA16_2 = input.LA(2);
+      if ( (LA16_2=='&') ) {
+        alt16=9;
+      }
+
+      else {
+        alt16=2;
+      }
+
+      }
+      break;
+    case '~':
+      {
+      alt16=3;
+      }
+      break;
+    case '|':
+      {
+      int LA16_4 = input.LA(2);
+      if ( (LA16_4=='|') ) {
+        alt16=11;
+      }
+
+      else {
+        alt16=4;
+      }
+
+      }
+      break;
+    case '<':
+      {
+      switch ( input.LA(2) ) {
+      case '<':
+        {
+        alt16=5;
+        }
+        break;
+      case '=':
+        {
+        alt16=18;
+        }
+        break;
+      default:
+        alt16=17;
+      }
+      }
+      break;
+    case '>':
+      {
+      switch ( input.LA(2) ) {
+      case '>':
+        {
+        int LA16_31 = input.LA(3);
+        if ( (LA16_31=='>') ) {
+          alt16=7;
+        }
+
+        else {
+          alt16=6;
+        }
+
+        }
+        break;
+      case '=':
+        {
+        alt16=16;
+        }
+        break;
+      default:
+        alt16=15;
+      }
+      }
+      break;
+    case '^':
+      {
+      alt16=8;
+      }
+      break;
+    case '!':
+      {
+      int LA16_8 = input.LA(2);
+      if ( (LA16_8=='=') ) {
+        alt16=19;
+      }
+
+      else {
+        alt16=10;
+      }
+
+      }
+      break;
+    case ':':
+      {
+      alt16=12;
+      }
+      break;
+    case ',':
+      {
+      alt16=13;
+      }
+      break;
+    case '=':
+      {
+      alt16=14;
+      }
+      break;
+    case '?':
+      {
+      alt16=20;
+      }
+      break;
+    case '/':
+      {
+      alt16=21;
+      }
+      break;
+    case '.':
+      {
+      int LA16_14 = input.LA(2);
+      if ( ((LA16_14 >= '0' && LA16_14 <= '9')) ) {
+        alt16=30;
+      }
+
+      else {
+        alt16=22;
+      }
+
+      }
+      break;
+    case '(':
+      {
+      alt16=23;
+      }
+      break;
+    case '%':
+      {
+      alt16=24;
+      }
+      break;
+    case '*':
+      {
+      alt16=25;
+      }
+      break;
+    case ')':
+      {
+      alt16=26;
+      }
+      break;
+    case '-':
+      {
+      alt16=27;
+      }
+      break;
+    case 'A':
+    case 'B':
+    case 'C':
+    case 'D':
+    case 'E':
+    case 'F':
+    case 'G':
+    case 'H':
+    case 'I':
+    case 'J':
+    case 'K':
+    case 'L':
+    case 'M':
+    case 'N':
+    case 'O':
+    case 'P':
+    case 'Q':
+    case 'R':
+    case 'S':
+    case 'T':
+    case 'U':
+    case 'V':
+    case 'W':
+    case 'X':
+    case 'Y':
+    case 'Z':
+    case '_':
+    case 'a':
+    case 'b':
+    case 'c':
+    case 'd':
+    case 'e':
+    case 'f':
+    case 'g':
+    case 'h':
+    case 'i':
+    case 'j':
+    case 'k':
+    case 'l':
+    case 'm':
+    case 'n':
+    case 'o':
+    case 'p':
+    case 'q':
+    case 'r':
+    case 's':
+    case 't':
+    case 'u':
+    case 'v':
+    case 'w':
+    case 'x':
+    case 'y':
+    case 'z':
+      {
+      alt16=28;
+      }
+      break;
+    case '\t':
+    case '\n':
+    case '\r':
+    case ' ':
+      {
+      alt16=29;
+      }
+      break;
+    case '0':
+      {
+      switch ( input.LA(2) ) {
+      case 'X':
+      case 'x':
+        {
+        alt16=32;
+        }
+        break;
+      case '0':
+      case '1':
+      case '2':
+      case '3':
+      case '4':
+      case '5':
+      case '6':
+      case '7':
+        {
+        alt16=31;
+        }
+        break;
+      default:
+        alt16=30;
+      }
+      }
+      break;
+    case '1':
+    case '2':
+    case '3':
+    case '4':
+    case '5':
+    case '6':
+    case '7':
+    case '8':
+    case '9':
+      {
+      alt16=30;
+      }
+      break;
+    default:
+      NoViableAltException nvae =
+        new NoViableAltException("", 16, 0, input);
+      throw nvae;
+    }
+    switch (alt16) {
+      case 1 :
+        // src/java/org/apache/lucene/expressions/js/Javascript.g:1:10: AT_ADD
+        {
+        mAT_ADD(); 
+
+        }
+        break;
+      case 2 :
+        // src/java/org/apache/lucene/expressions/js/Javascript.g:1:17: AT_BIT_AND
+        {
+        mAT_BIT_AND(); 
+
+        }
+        break;
+      case 3 :
+        // src/java/org/apache/lucene/expressions/js/Javascript.g:1:28: AT_BIT_NOT
+        {
+        mAT_BIT_NOT(); 
+
+        }
+        break;
+      case 4 :
+        // src/java/org/apache/lucene/expressions/js/Javascript.g:1:39: AT_BIT_OR
+        {
+        mAT_BIT_OR(); 
+
+        }
+        break;
+      case 5 :
+        // src/java/org/apache/lucene/expressions/js/Javascript.g:1:49: AT_BIT_SHL
+        {
+        mAT_BIT_SHL(); 
+
+        }
+        break;
+      case 6 :
+        // src/java/org/apache/lucene/expressions/js/Javascript.g:1:60: AT_BIT_SHR
+        {
+        mAT_BIT_SHR(); 
+
+        }
+        break;
+      case 7 :
+        // src/java/org/apache/lucene/expressions/js/Javascript.g:1:71: AT_BIT_SHU
+        {
+        mAT_BIT_SHU(); 
+
+        }
+        break;
+      case 8 :
+        // src/java/org/apache/lucene/expressions/js/Javascript.g:1:82: AT_BIT_XOR
+        {
+        mAT_BIT_XOR(); 
+
+        }
+        break;
+      case 9 :
+        // src/java/org/apache/lucene/expressions/js/Javascript.g:1:93: AT_BOOL_AND
+        {
+        mAT_BOOL_AND(); 
+
+        }
+        break;
+      case 10 :
+        // src/java/org/apache/lucene/expressions/js/Javascript.g:1:105: AT_BOOL_NOT
+        {
+        mAT_BOOL_NOT(); 
+
+        }
+        break;
+      case 11 :
+        // src/java/org/apache/lucene/expressions/js/Javascript.g:1:117: AT_BOOL_OR
+        {
+        mAT_BOOL_OR(); 
+
+        }
+        break;
+      case 12 :
+        // src/java/org/apache/lucene/expressions/js/Javascript.g:1:128: AT_COLON
+        {
+        mAT_COLON(); 
+
+        }
+        break;
+      case 13 :
+        // src/java/org/apache/lucene/expressions/js/Javascript.g:1:137: AT_COMMA
+        {
+        mAT_COMMA(); 
+
+        }
+        break;
+      case 14 :
+        // src/java/org/apache/lucene/expressions/js/Javascript.g:1:146: AT_COMP_EQ
+        {
+        mAT_COMP_EQ(); 
+
+        }
+        break;
+      case 15 :
+        // src/java/org/apache/lucene/expressions/js/Javascript.g:1:157: AT_COMP_GT
+        {
+        mAT_COMP_GT(); 
+
+        }
+        break;
+      case 16 :
+        // src/java/org/apache/lucene/expressions/js/Javascript.g:1:168: AT_COMP_GTE
+        {
+        mAT_COMP_GTE(); 
+
+        }
+        break;
+      case 17 :
+        // src/java/org/apache/lucene/expressions/js/Javascript.g:1:180: AT_COMP_LT
+        {
+        mAT_COMP_LT(); 
+
+        }
+        break;
+      case 18 :
+        // src/java/org/apache/lucene/expressions/js/Javascript.g:1:191: AT_COMP_LTE
+        {
+        mAT_COMP_LTE(); 
+
+        }
+        break;
+      case 19 :
+        // src/java/org/apache/lucene/expressions/js/Javascript.g:1:203: AT_COMP_NEQ
+        {
+        mAT_COMP_NEQ(); 
+
+        }
+        break;
+      case 20 :
+        // src/java/org/apache/lucene/expressions/js/Javascript.g:1:215: AT_COND_QUE
+        {
+        mAT_COND_QUE(); 
+
+        }
+        break;
+      case 21 :
+        // src/java/org/apache/lucene/expressions/js/Javascript.g:1:227: AT_DIVIDE
+        {
+        mAT_DIVIDE(); 
+
+        }
+        break;
+      case 22 :
+        // src/java/org/apache/lucene/expressions/js/Javascript.g:1:237: AT_DOT
+        {
+        mAT_DOT(); 
+
+        }
+        break;
+      case 23 :
+        // src/java/org/apache/lucene/expressions/js/Javascript.g:1:244: AT_LPAREN
+        {
+        mAT_LPAREN(); 
+
+        }
+        break;
+      case 24 :
+        // src/java/org/apache/lucene/expressions/js/Javascript.g:1:254: AT_MODULO
+        {
+        mAT_MODULO(); 
+
+        }
+        break;
+      case 25 :
+        // src/java/org/apache/lucene/expressions/js/Javascript.g:1:264: AT_MULTIPLY
+        {
+        mAT_MULTIPLY(); 
+
+        }
+        break;
+      case 26 :
+        // src/java/org/apache/lucene/expressions/js/Javascript.g:1:276: AT_RPAREN
+        {
+        mAT_RPAREN(); 
+
+        }
+        break;
+      case 27 :
+        // src/java/org/apache/lucene/expressions/js/Javascript.g:1:286: AT_SUBTRACT
+        {
+        mAT_SUBTRACT(); 
+
+        }
+        break;
+      case 28 :
+        // src/java/org/apache/lucene/expressions/js/Javascript.g:1:298: ID
+        {
+        mID(); 
+
+        }
+        break;
+      case 29 :
+        // src/java/org/apache/lucene/expressions/js/Javascript.g:1:301: WS
+        {
+        mWS(); 
+
+        }
+        break;
+      case 30 :
+        // src/java/org/apache/lucene/expressions/js/Javascript.g:1:304: DECIMAL
+        {
+        mDECIMAL(); 
+
+        }
+        break;
+      case 31 :
+        // src/java/org/apache/lucene/expressions/js/Javascript.g:1:312: OCTAL
+        {
+        mOCTAL(); 
+
+        }
+        break;
+      case 32 :
+        // src/java/org/apache/lucene/expressions/js/Javascript.g:1:318: HEX
+        {
+        mHEX(); 
+
+        }
+        break;
+
+    }
+  }
+
+
+  protected DFA8 dfa8 = new DFA8(this);
+  static final String DFA8_eotS =
+    "\1\uffff\2\4\3\uffff\1\4";
+  static final String DFA8_eofS =
+    "\7\uffff";
+  static final String DFA8_minS =
+    "\3\56\3\uffff\1\56";
+  static final String DFA8_maxS =
+    "\1\71\1\56\1\71\3\uffff\1\71";
+  static final String DFA8_acceptS =
+    "\3\uffff\1\2\1\3\1\1\1\uffff";
+  static final String DFA8_specialS =
+    "\7\uffff}>";
+  static final String[] DFA8_transitionS = {
+      "\1\3\1\uffff\1\1\11\2",
+      "\1\5",
+      "\1\5\1\uffff\12\6",
+      "",
+      "",
+      "",
+      "\1\5\1\uffff\12\6"
+  };
+
+  static final short[] DFA8_eot = DFA.unpackEncodedString(DFA8_eotS);
+  static final short[] DFA8_eof = DFA.unpackEncodedString(DFA8_eofS);
+  static final char[] DFA8_min = DFA.unpackEncodedStringToUnsignedChars(DFA8_minS);
+  static final char[] DFA8_max = DFA.unpackEncodedStringToUnsignedChars(DFA8_maxS);
+  static final short[] DFA8_accept = DFA.unpackEncodedString(DFA8_acceptS);
+  static final short[] DFA8_special = DFA.unpackEncodedString(DFA8_specialS);
+  static final short[][] DFA8_transition;
+
+  static {
+    int numStates = DFA8_transitionS.length;
+    DFA8_transition = new short[numStates][];
+    for (int i=0; i<numStates; i++) {
+      DFA8_transition[i] = DFA.unpackEncodedString(DFA8_transitionS[i]);
+    }
+  }
+
+  protected class DFA8 extends DFA {
+
+    public DFA8(BaseRecognizer recognizer) {
+      this.recognizer = recognizer;
+      this.decisionNumber = 8;
+      this.eot = DFA8_eot;
+      this.eof = DFA8_eof;
+      this.min = DFA8_min;
+      this.max = DFA8_max;
+      this.accept = DFA8_accept;
+      this.special = DFA8_special;
+      this.transition = DFA8_transition;
+    }
+    @Override
+    public String getDescription() {
+      return "341:1: DECIMAL : ( DECIMALINTEGER AT_DOT ( DECIMALDIGIT )* ( EXPONENT )? | AT_DOT ( DECIMALDIGIT )+ ( EXPONENT )? | DECIMALINTEGER ( EXPONENT )? );";
+    }
+  }
+
+}
diff --git a/lucene/expressions/src/java/org/apache/lucene/expressions/js/JavascriptParser.java b/lucene/expressions/src/java/org/apache/lucene/expressions/js/JavascriptParser.java
new file mode 100644
index 0000000..732a658
--- /dev/null
+++ b/lucene/expressions/src/java/org/apache/lucene/expressions/js/JavascriptParser.java
@@ -0,0 +1,1963 @@
+// ANTLR GENERATED CODE: DO NOT EDIT
+
+package org.apache.lucene.expressions.js;
+    
+import java.text.ParseException;
+
+
+import org.antlr.runtime.*;
+import java.util.Stack;
+import java.util.List;
+import java.util.ArrayList;
+
+import org.antlr.runtime.tree.*;
+
+
+@SuppressWarnings("all")
+class JavascriptParser extends Parser {
+  public static final String[] tokenNames = new String[] {
+    "<invalid>", "<EOR>", "<DOWN>", "<UP>", "AT_ADD", "AT_BIT_AND", "AT_BIT_NOT", 
+    "AT_BIT_OR", "AT_BIT_SHL", "AT_BIT_SHR", "AT_BIT_SHU", "AT_BIT_XOR", "AT_BOOL_AND", 
+    "AT_BOOL_NOT", "AT_BOOL_OR", "AT_CALL", "AT_COLON", "AT_COMMA", "AT_COMP_EQ", 
+    "AT_COMP_GT", "AT_COMP_GTE", "AT_COMP_LT", "AT_COMP_LTE", "AT_COMP_NEQ", 
+    "AT_COND_QUE", "AT_DIVIDE", "AT_DOT", "AT_LPAREN", "AT_MODULO", "AT_MULTIPLY", 
+    "AT_NEGATE", "AT_RPAREN", "AT_SUBTRACT", "DECIMAL", "DECIMALDIGIT", "DECIMALINTEGER", 
+    "EXPONENT", "HEX", "HEXDIGIT", "ID", "OCTAL", "OCTALDIGIT", "WS"
+  };
+  public static final int EOF=-1;
+  public static final int AT_ADD=4;
+  public static final int AT_BIT_AND=5;
+  public static final int AT_BIT_NOT=6;
+  public static final int AT_BIT_OR=7;
+  public static final int AT_BIT_SHL=8;
+  public static final int AT_BIT_SHR=9;
+  public static final int AT_BIT_SHU=10;
+  public static final int AT_BIT_XOR=11;
+  public static final int AT_BOOL_AND=12;
+  public static final int AT_BOOL_NOT=13;
+  public static final int AT_BOOL_OR=14;
+  public static final int AT_CALL=15;
+  public static final int AT_COLON=16;
+  public static final int AT_COMMA=17;
+  public static final int AT_COMP_EQ=18;
+  public static final int AT_COMP_GT=19;
+  public static final int AT_COMP_GTE=20;
+  public static final int AT_COMP_LT=21;
+  public static final int AT_COMP_LTE=22;
+  public static final int AT_COMP_NEQ=23;
+  public static final int AT_COND_QUE=24;
+  public static final int AT_DIVIDE=25;
+  public static final int AT_DOT=26;
+  public static final int AT_LPAREN=27;
+  public static final int AT_MODULO=28;
+  public static final int AT_MULTIPLY=29;
+  public static final int AT_NEGATE=30;
+  public static final int AT_RPAREN=31;
+  public static final int AT_SUBTRACT=32;
+  public static final int DECIMAL=33;
+  public static final int DECIMALDIGIT=34;
+  public static final int DECIMALINTEGER=35;
+  public static final int EXPONENT=36;
+  public static final int HEX=37;
+  public static final int HEXDIGIT=38;
+  public static final int ID=39;
+  public static final int OCTAL=40;
+  public static final int OCTALDIGIT=41;
+  public static final int WS=42;
+
+  // delegates
+  public Parser[] getDelegates() {
+    return new Parser[] {};
+  }
+
+  // delegators
+
+
+  public JavascriptParser(TokenStream input) {
+    this(input, new RecognizerSharedState());
+  }
+  public JavascriptParser(TokenStream input, RecognizerSharedState state) {
+    super(input, state);
+  }
+
+  protected TreeAdaptor adaptor = new CommonTreeAdaptor();
+
+  public void setTreeAdaptor(TreeAdaptor adaptor) {
+    this.adaptor = adaptor;
+  }
+  public TreeAdaptor getTreeAdaptor() {
+    return adaptor;
+  }
+  @Override public String[] getTokenNames() { return JavascriptParser.tokenNames; }
+  @Override public String getGrammarFileName() { return "src/java/org/apache/lucene/expressions/js/Javascript.g"; }
+
+
+
+  @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() + "'";
+      }
+  }
+
+
+
+  public static class expression_return extends ParserRuleReturnScope {
+    CommonTree tree;
+    @Override
+    public CommonTree getTree() { return tree; }
+  };
+
+
+  // $ANTLR start "expression"
+  // src/java/org/apache/lucene/expressions/js/Javascript.g:250:1: expression : conditional EOF !;
+  public final JavascriptParser.expression_return expression() throws RecognitionException {
+    JavascriptParser.expression_return retval = new JavascriptParser.expression_return();
+    retval.start = input.LT(1);
+
+    CommonTree root_0 = null;
+
+    Token EOF2=null;
+    ParserRuleReturnScope conditional1 =null;
+
+    CommonTree EOF2_tree=null;
+
+    try {
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:251:5: ( conditional EOF !)
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:251:7: conditional EOF !
+      {
+      root_0 = (CommonTree)adaptor.nil();
+
+
+      pushFollow(FOLLOW_conditional_in_expression737);
+      conditional1=conditional();
+      state._fsp--;
+
+      adaptor.addChild(root_0, conditional1.getTree());
+
+      EOF2=(Token)match(input,EOF,FOLLOW_EOF_in_expression739); 
+      }
+
+      retval.stop = input.LT(-1);
+
+      retval.tree = (CommonTree)adaptor.rulePostProcessing(root_0);
+      adaptor.setTokenBoundaries(retval.tree, retval.start, retval.stop);
+
+    }
+    catch (RecognitionException re) {
+      reportError(re);
+      recover(input,re);
+      retval.tree = (CommonTree)adaptor.errorNode(input, retval.start, input.LT(-1), re);
+    }
+    finally {
+      // do for sure before leaving
+    }
+    return retval;
+  }
+  // $ANTLR end "expression"
+
+
+  public static class conditional_return extends ParserRuleReturnScope {
+    CommonTree tree;
+    @Override
+    public CommonTree getTree() { return tree; }
+  };
+
+
+  // $ANTLR start "conditional"
+  // src/java/org/apache/lucene/expressions/js/Javascript.g:254:1: conditional : logical_or ( AT_COND_QUE ^ conditional AT_COLON ! conditional )? ;
+  public final JavascriptParser.conditional_return conditional() throws RecognitionException {
+    JavascriptParser.conditional_return retval = new JavascriptParser.conditional_return();
+    retval.start = input.LT(1);
+
+    CommonTree root_0 = null;
+
+    Token AT_COND_QUE4=null;
+    Token AT_COLON6=null;
+    ParserRuleReturnScope logical_or3 =null;
+    ParserRuleReturnScope conditional5 =null;
+    ParserRuleReturnScope conditional7 =null;
+
+    CommonTree AT_COND_QUE4_tree=null;
+    CommonTree AT_COLON6_tree=null;
+
+    try {
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:255:5: ( logical_or ( AT_COND_QUE ^ conditional AT_COLON ! conditional )? )
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:255:7: logical_or ( AT_COND_QUE ^ conditional AT_COLON ! conditional )?
+      {
+      root_0 = (CommonTree)adaptor.nil();
+
+
+      pushFollow(FOLLOW_logical_or_in_conditional757);
+      logical_or3=logical_or();
+      state._fsp--;
+
+      adaptor.addChild(root_0, logical_or3.getTree());
+
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:255:18: ( AT_COND_QUE ^ conditional AT_COLON ! conditional )?
+      int alt1=2;
+      int LA1_0 = input.LA(1);
+      if ( (LA1_0==AT_COND_QUE) ) {
+        alt1=1;
+      }
+      switch (alt1) {
+        case 1 :
+          // src/java/org/apache/lucene/expressions/js/Javascript.g:255:19: AT_COND_QUE ^ conditional AT_COLON ! conditional
+          {
+          AT_COND_QUE4=(Token)match(input,AT_COND_QUE,FOLLOW_AT_COND_QUE_in_conditional760); 
+          AT_COND_QUE4_tree = (CommonTree)adaptor.create(AT_COND_QUE4);
+          root_0 = (CommonTree)adaptor.becomeRoot(AT_COND_QUE4_tree, root_0);
+
+          pushFollow(FOLLOW_conditional_in_conditional763);
+          conditional5=conditional();
+          state._fsp--;
+
+          adaptor.addChild(root_0, conditional5.getTree());
+
+          AT_COLON6=(Token)match(input,AT_COLON,FOLLOW_AT_COLON_in_conditional765); 
+          pushFollow(FOLLOW_conditional_in_conditional768);
+          conditional7=conditional();
+          state._fsp--;
+
+          adaptor.addChild(root_0, conditional7.getTree());
+
+          }
+          break;
+
+      }
+
+      }
+
+      retval.stop = input.LT(-1);
+
+      retval.tree = (CommonTree)adaptor.rulePostProcessing(root_0);
+      adaptor.setTokenBoundaries(retval.tree, retval.start, retval.stop);
+
+    }
+    catch (RecognitionException re) {
+      reportError(re);
+      recover(input,re);
+      retval.tree = (CommonTree)adaptor.errorNode(input, retval.start, input.LT(-1), re);
+    }
+    finally {
+      // do for sure before leaving
+    }
+    return retval;
+  }
+  // $ANTLR end "conditional"
+
+
+  public static class logical_or_return extends ParserRuleReturnScope {
+    CommonTree tree;
+    @Override
+    public CommonTree getTree() { return tree; }
+  };
+
+
+  // $ANTLR start "logical_or"
+  // src/java/org/apache/lucene/expressions/js/Javascript.g:258:1: logical_or : logical_and ( AT_BOOL_OR ^ logical_and )* ;
+  public final JavascriptParser.logical_or_return logical_or() throws RecognitionException {
+    JavascriptParser.logical_or_return retval = new JavascriptParser.logical_or_return();
+    retval.start = input.LT(1);
+
+    CommonTree root_0 = null;
+
+    Token AT_BOOL_OR9=null;
+    ParserRuleReturnScope logical_and8 =null;
+    ParserRuleReturnScope logical_and10 =null;
+
+    CommonTree AT_BOOL_OR9_tree=null;
+
+    try {
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:259:5: ( logical_and ( AT_BOOL_OR ^ logical_and )* )
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:259:7: logical_and ( AT_BOOL_OR ^ logical_and )*
+      {
+      root_0 = (CommonTree)adaptor.nil();
+
+
+      pushFollow(FOLLOW_logical_and_in_logical_or787);
+      logical_and8=logical_and();
+      state._fsp--;
+
+      adaptor.addChild(root_0, logical_and8.getTree());
+
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:259:19: ( AT_BOOL_OR ^ logical_and )*
+      loop2:
+      while (true) {
+        int alt2=2;
+        int LA2_0 = input.LA(1);
+        if ( (LA2_0==AT_BOOL_OR) ) {
+          alt2=1;
+        }
+
+        switch (alt2) {
+        case 1 :
+          // src/java/org/apache/lucene/expressions/js/Javascript.g:259:20: AT_BOOL_OR ^ logical_and
+          {
+          AT_BOOL_OR9=(Token)match(input,AT_BOOL_OR,FOLLOW_AT_BOOL_OR_in_logical_or790); 
+          AT_BOOL_OR9_tree = (CommonTree)adaptor.create(AT_BOOL_OR9);
+          root_0 = (CommonTree)adaptor.becomeRoot(AT_BOOL_OR9_tree, root_0);
+
+          pushFollow(FOLLOW_logical_and_in_logical_or793);
+          logical_and10=logical_and();
+          state._fsp--;
+
+          adaptor.addChild(root_0, logical_and10.getTree());
+
+          }
+          break;
+
+        default :
+          break loop2;
+        }
+      }
+
+      }
+
+      retval.stop = input.LT(-1);
+
+      retval.tree = (CommonTree)adaptor.rulePostProcessing(root_0);
+      adaptor.setTokenBoundaries(retval.tree, retval.start, retval.stop);
+
+    }
+    catch (RecognitionException re) {
+      reportError(re);
+      recover(input,re);
+      retval.tree = (CommonTree)adaptor.errorNode(input, retval.start, input.LT(-1), re);
+    }
+    finally {
+      // do for sure before leaving
+    }
+    return retval;
+  }
+  // $ANTLR end "logical_or"
+
+
+  public static class logical_and_return extends ParserRuleReturnScope {
+    CommonTree tree;
+    @Override
+    public CommonTree getTree() { return tree; }
+  };
+
+
+  // $ANTLR start "logical_and"
+  // src/java/org/apache/lucene/expressions/js/Javascript.g:262:1: logical_and : bitwise_or ( AT_BOOL_AND ^ bitwise_or )* ;
+  public final JavascriptParser.logical_and_return logical_and() throws RecognitionException {
+    JavascriptParser.logical_and_return retval = new JavascriptParser.logical_and_return();
+    retval.start = input.LT(1);
+
+    CommonTree root_0 = null;
+
+    Token AT_BOOL_AND12=null;
+    ParserRuleReturnScope bitwise_or11 =null;
+    ParserRuleReturnScope bitwise_or13 =null;
+
+    CommonTree AT_BOOL_AND12_tree=null;
+
+    try {
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:263:5: ( bitwise_or ( AT_BOOL_AND ^ bitwise_or )* )
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:263:7: bitwise_or ( AT_BOOL_AND ^ bitwise_or )*
+      {
+      root_0 = (CommonTree)adaptor.nil();
+
+
+      pushFollow(FOLLOW_bitwise_or_in_logical_and812);
+      bitwise_or11=bitwise_or();
+      state._fsp--;
+
+      adaptor.addChild(root_0, bitwise_or11.getTree());
+
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:263:18: ( AT_BOOL_AND ^ bitwise_or )*
+      loop3:
+      while (true) {
+        int alt3=2;
+        int LA3_0 = input.LA(1);
+        if ( (LA3_0==AT_BOOL_AND) ) {
+          alt3=1;
+        }
+
+        switch (alt3) {
+        case 1 :
+          // src/java/org/apache/lucene/expressions/js/Javascript.g:263:19: AT_BOOL_AND ^ bitwise_or
+          {
+          AT_BOOL_AND12=(Token)match(input,AT_BOOL_AND,FOLLOW_AT_BOOL_AND_in_logical_and815); 
+          AT_BOOL_AND12_tree = (CommonTree)adaptor.create(AT_BOOL_AND12);
+          root_0 = (CommonTree)adaptor.becomeRoot(AT_BOOL_AND12_tree, root_0);
+
+          pushFollow(FOLLOW_bitwise_or_in_logical_and818);
+          bitwise_or13=bitwise_or();
+          state._fsp--;
+
+          adaptor.addChild(root_0, bitwise_or13.getTree());
+
+          }
+          break;
+
+        default :
+          break loop3;
+        }
+      }
+
+      }
+
+      retval.stop = input.LT(-1);
+
+      retval.tree = (CommonTree)adaptor.rulePostProcessing(root_0);
+      adaptor.setTokenBoundaries(retval.tree, retval.start, retval.stop);
+
+    }
+    catch (RecognitionException re) {
+      reportError(re);
+      recover(input,re);
+      retval.tree = (CommonTree)adaptor.errorNode(input, retval.start, input.LT(-1), re);
+    }
+    finally {
+      // do for sure before leaving
+    }
+    return retval;
+  }
+  // $ANTLR end "logical_and"
+
+
+  public static class bitwise_or_return extends ParserRuleReturnScope {
+    CommonTree tree;
+    @Override
+    public CommonTree getTree() { return tree; }
+  };
+
+
+  // $ANTLR start "bitwise_or"
+  // src/java/org/apache/lucene/expressions/js/Javascript.g:266:1: bitwise_or : bitwise_xor ( AT_BIT_OR ^ bitwise_xor )* ;
+  public final JavascriptParser.bitwise_or_return bitwise_or() throws RecognitionException {
+    JavascriptParser.bitwise_or_return retval = new JavascriptParser.bitwise_or_return();
+    retval.start = input.LT(1);
+
+    CommonTree root_0 = null;
+
+    Token AT_BIT_OR15=null;
+    ParserRuleReturnScope bitwise_xor14 =null;
+    ParserRuleReturnScope bitwise_xor16 =null;
+
+    CommonTree AT_BIT_OR15_tree=null;
+
+    try {
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:267:5: ( bitwise_xor ( AT_BIT_OR ^ bitwise_xor )* )
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:267:7: bitwise_xor ( AT_BIT_OR ^ bitwise_xor )*
+      {
+      root_0 = (CommonTree)adaptor.nil();
+
+
+      pushFollow(FOLLOW_bitwise_xor_in_bitwise_or837);
+      bitwise_xor14=bitwise_xor();
+      state._fsp--;
+
+      adaptor.addChild(root_0, bitwise_xor14.getTree());
+
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:267:19: ( AT_BIT_OR ^ bitwise_xor )*
+      loop4:
+      while (true) {
+        int alt4=2;
+        int LA4_0 = input.LA(1);
+        if ( (LA4_0==AT_BIT_OR) ) {
+          alt4=1;
+        }
+
+        switch (alt4) {
+        case 1 :
+          // src/java/org/apache/lucene/expressions/js/Javascript.g:267:20: AT_BIT_OR ^ bitwise_xor
+          {
+          AT_BIT_OR15=(Token)match(input,AT_BIT_OR,FOLLOW_AT_BIT_OR_in_bitwise_or840); 
+          AT_BIT_OR15_tree = (CommonTree)adaptor.create(AT_BIT_OR15);
+          root_0 = (CommonTree)adaptor.becomeRoot(AT_BIT_OR15_tree, root_0);
+
+          pushFollow(FOLLOW_bitwise_xor_in_bitwise_or843);
+          bitwise_xor16=bitwise_xor();
+          state._fsp--;
+
+          adaptor.addChild(root_0, bitwise_xor16.getTree());
+
+          }
+          break;
+
+        default :
+          break loop4;
+        }
+      }
+
+      }
+
+      retval.stop = input.LT(-1);
+
+      retval.tree = (CommonTree)adaptor.rulePostProcessing(root_0);
+      adaptor.setTokenBoundaries(retval.tree, retval.start, retval.stop);
+
+    }
+    catch (RecognitionException re) {
+      reportError(re);
+      recover(input,re);
+      retval.tree = (CommonTree)adaptor.errorNode(input, retval.start, input.LT(-1), re);
+    }
+    finally {
+      // do for sure before leaving
+    }
+    return retval;
+  }
+  // $ANTLR end "bitwise_or"
+
+
+  public static class bitwise_xor_return extends ParserRuleReturnScope {
+    CommonTree tree;
+    @Override
+    public CommonTree getTree() { return tree; }
+  };
+
+
+  // $ANTLR start "bitwise_xor"
+  // src/java/org/apache/lucene/expressions/js/Javascript.g:270:1: bitwise_xor : bitwise_and ( AT_BIT_XOR ^ bitwise_and )* ;
+  public final JavascriptParser.bitwise_xor_return bitwise_xor() throws RecognitionException {
+    JavascriptParser.bitwise_xor_return retval = new JavascriptParser.bitwise_xor_return();
+    retval.start = input.LT(1);
+
+    CommonTree root_0 = null;
+
+    Token AT_BIT_XOR18=null;
+    ParserRuleReturnScope bitwise_and17 =null;
+    ParserRuleReturnScope bitwise_and19 =null;
+
+    CommonTree AT_BIT_XOR18_tree=null;
+
+    try {
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:271:5: ( bitwise_and ( AT_BIT_XOR ^ bitwise_and )* )
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:271:7: bitwise_and ( AT_BIT_XOR ^ bitwise_and )*
+      {
+      root_0 = (CommonTree)adaptor.nil();
+
+
+      pushFollow(FOLLOW_bitwise_and_in_bitwise_xor862);
+      bitwise_and17=bitwise_and();
+      state._fsp--;
+
+      adaptor.addChild(root_0, bitwise_and17.getTree());
+
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:271:19: ( AT_BIT_XOR ^ bitwise_and )*
+      loop5:
+      while (true) {
+        int alt5=2;
+        int LA5_0 = input.LA(1);
+        if ( (LA5_0==AT_BIT_XOR) ) {
+          alt5=1;
+        }
+
+        switch (alt5) {
+        case 1 :
+          // src/java/org/apache/lucene/expressions/js/Javascript.g:271:20: AT_BIT_XOR ^ bitwise_and
+          {
+          AT_BIT_XOR18=(Token)match(input,AT_BIT_XOR,FOLLOW_AT_BIT_XOR_in_bitwise_xor865); 
+          AT_BIT_XOR18_tree = (CommonTree)adaptor.create(AT_BIT_XOR18);
+          root_0 = (CommonTree)adaptor.becomeRoot(AT_BIT_XOR18_tree, root_0);
+
+          pushFollow(FOLLOW_bitwise_and_in_bitwise_xor868);
+          bitwise_and19=bitwise_and();
+          state._fsp--;
+
+          adaptor.addChild(root_0, bitwise_and19.getTree());
+
+          }
+          break;
+
+        default :
+          break loop5;
+        }
+      }
+
+      }
+
+      retval.stop = input.LT(-1);
+
+      retval.tree = (CommonTree)adaptor.rulePostProcessing(root_0);
+      adaptor.setTokenBoundaries(retval.tree, retval.start, retval.stop);
+
+    }
+    catch (RecognitionException re) {
+      reportError(re);
+      recover(input,re);
+      retval.tree = (CommonTree)adaptor.errorNode(input, retval.start, input.LT(-1), re);
+    }
+    finally {
+      // do for sure before leaving
+    }
+    return retval;
+  }
+  // $ANTLR end "bitwise_xor"
+
+
+  public static class bitwise_and_return extends ParserRuleReturnScope {
+    CommonTree tree;
+    @Override
+    public CommonTree getTree() { return tree; }
+  };
+
+
+  // $ANTLR start "bitwise_and"
+  // src/java/org/apache/lucene/expressions/js/Javascript.g:274:1: bitwise_and : equality ( AT_BIT_AND ^ equality )* ;
+  public final JavascriptParser.bitwise_and_return bitwise_and() throws RecognitionException {
+    JavascriptParser.bitwise_and_return retval = new JavascriptParser.bitwise_and_return();
+    retval.start = input.LT(1);
+
+    CommonTree root_0 = null;
+
+    Token AT_BIT_AND21=null;
+    ParserRuleReturnScope equality20 =null;
+    ParserRuleReturnScope equality22 =null;
+
+    CommonTree AT_BIT_AND21_tree=null;
+
+    try {
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:275:5: ( equality ( AT_BIT_AND ^ equality )* )
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:275:8: equality ( AT_BIT_AND ^ equality )*
+      {
+      root_0 = (CommonTree)adaptor.nil();
+
+
+      pushFollow(FOLLOW_equality_in_bitwise_and888);
+      equality20=equality();
+      state._fsp--;
+
+      adaptor.addChild(root_0, equality20.getTree());
+
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:275:17: ( AT_BIT_AND ^ equality )*
+      loop6:
+      while (true) {
+        int alt6=2;
+        int LA6_0 = input.LA(1);
+        if ( (LA6_0==AT_BIT_AND) ) {
+          alt6=1;
+        }
+
+        switch (alt6) {
+        case 1 :
+          // src/java/org/apache/lucene/expressions/js/Javascript.g:275:18: AT_BIT_AND ^ equality
+          {
+          AT_BIT_AND21=(Token)match(input,AT_BIT_AND,FOLLOW_AT_BIT_AND_in_bitwise_and891); 
+          AT_BIT_AND21_tree = (CommonTree)adaptor.create(AT_BIT_AND21);
+          root_0 = (CommonTree)adaptor.becomeRoot(AT_BIT_AND21_tree, root_0);
+
+          pushFollow(FOLLOW_equality_in_bitwise_and894);
+          equality22=equality();
+          state._fsp--;
+
+          adaptor.addChild(root_0, equality22.getTree());
+
+          }
+          break;
+
+        default :
+          break loop6;
+        }
+      }
+
+      }
+
+      retval.stop = input.LT(-1);
+
+      retval.tree = (CommonTree)adaptor.rulePostProcessing(root_0);
+      adaptor.setTokenBoundaries(retval.tree, retval.start, retval.stop);
+
+    }
+    catch (RecognitionException re) {
+      reportError(re);
+      recover(input,re);
+      retval.tree = (CommonTree)adaptor.errorNode(input, retval.start, input.LT(-1), re);
+    }
+    finally {
+      // do for sure before leaving
+    }
+    return retval;
+  }
+  // $ANTLR end "bitwise_and"
+
+
+  public static class equality_return extends ParserRuleReturnScope {
+    CommonTree tree;
+    @Override
+    public CommonTree getTree() { return tree; }
+  };
+
+
+  // $ANTLR start "equality"
+  // src/java/org/apache/lucene/expressions/js/Javascript.g:278:1: equality : relational ( ( AT_COMP_EQ | AT_COMP_NEQ ) ^ relational )* ;
+  public final JavascriptParser.equality_return equality() throws RecognitionException {
+    JavascriptParser.equality_return retval = new JavascriptParser.equality_return();
+    retval.start = input.LT(1);
+
+    CommonTree root_0 = null;
+
+    Token set24=null;
+    ParserRuleReturnScope relational23 =null;
+    ParserRuleReturnScope relational25 =null;
+
+    CommonTree set24_tree=null;
+
+    try {
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:279:5: ( relational ( ( AT_COMP_EQ | AT_COMP_NEQ ) ^ relational )* )
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:279:7: relational ( ( AT_COMP_EQ | AT_COMP_NEQ ) ^ relational )*
+      {
+      root_0 = (CommonTree)adaptor.nil();
+
+
+      pushFollow(FOLLOW_relational_in_equality913);
+      relational23=relational();
+      state._fsp--;
+
+      adaptor.addChild(root_0, relational23.getTree());
+
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:279:18: ( ( AT_COMP_EQ | AT_COMP_NEQ ) ^ relational )*
+      loop7:
+      while (true) {
+        int alt7=2;
+        int LA7_0 = input.LA(1);
+        if ( (LA7_0==AT_COMP_EQ||LA7_0==AT_COMP_NEQ) ) {
+          alt7=1;
+        }
+
+        switch (alt7) {
+        case 1 :
+          // src/java/org/apache/lucene/expressions/js/Javascript.g:279:19: ( AT_COMP_EQ | AT_COMP_NEQ ) ^ relational
+          {
+          set24=input.LT(1);
+          set24=input.LT(1);
+          if ( input.LA(1)==AT_COMP_EQ||input.LA(1)==AT_COMP_NEQ ) {
+            input.consume();
+            root_0 = (CommonTree)adaptor.becomeRoot((CommonTree)adaptor.create(set24), root_0);
+            state.errorRecovery=false;
+          }
+          else {
+            MismatchedSetException mse = new MismatchedSetException(null,input);
+            throw mse;
+          }
+          pushFollow(FOLLOW_relational_in_equality925);
+          relational25=relational();
+          state._fsp--;
+
+          adaptor.addChild(root_0, relational25.getTree());
+
+          }
+          break;
+
+        default :
+          break loop7;
+        }
+      }
+
+      }
+
+      retval.stop = input.LT(-1);
+
+      retval.tree = (CommonTree)adaptor.rulePostProcessing(root_0);
+      adaptor.setTokenBoundaries(retval.tree, retval.start, retval.stop);
+
+    }
+    catch (RecognitionException re) {
+      reportError(re);
+      recover(input,re);
+      retval.tree = (CommonTree)adaptor.errorNode(input, retval.start, input.LT(-1), re);
+    }
+    finally {
+      // do for sure before leaving
+    }
+    return retval;
+  }
+  // $ANTLR end "equality"
+
+
+  public static class relational_return extends ParserRuleReturnScope {
+    CommonTree tree;
+    @Override
+    public CommonTree getTree() { return tree; }
+  };
+
+
+  // $ANTLR start "relational"
+  // src/java/org/apache/lucene/expressions/js/Javascript.g:282:1: relational : shift ( ( AT_COMP_LT | AT_COMP_GT | AT_COMP_LTE | AT_COMP_GTE ) ^ shift )* ;
+  public final JavascriptParser.relational_return relational() throws RecognitionException {
+    JavascriptParser.relational_return retval = new JavascriptParser.relational_return();
+    retval.start = input.LT(1);
+
+    CommonTree root_0 = null;
+
+    Token set27=null;
+    ParserRuleReturnScope shift26 =null;
+    ParserRuleReturnScope shift28 =null;
+
+    CommonTree set27_tree=null;
+
+    try {
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:283:5: ( shift ( ( AT_COMP_LT | AT_COMP_GT | AT_COMP_LTE | AT_COMP_GTE ) ^ shift )* )
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:283:7: shift ( ( AT_COMP_LT | AT_COMP_GT | AT_COMP_LTE | AT_COMP_GTE ) ^ shift )*
+      {
+      root_0 = (CommonTree)adaptor.nil();
+
+
+      pushFollow(FOLLOW_shift_in_relational944);
+      shift26=shift();
+      state._fsp--;
+
+      adaptor.addChild(root_0, shift26.getTree());
+
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:283:13: ( ( AT_COMP_LT | AT_COMP_GT | AT_COMP_LTE | AT_COMP_GTE ) ^ shift )*
+      loop8:
+      while (true) {
+        int alt8=2;
+        int LA8_0 = input.LA(1);
+        if ( ((LA8_0 >= AT_COMP_GT && LA8_0 <= AT_COMP_LTE)) ) {
+          alt8=1;
+        }
+
+        switch (alt8) {
+        case 1 :
+          // src/java/org/apache/lucene/expressions/js/Javascript.g:283:14: ( AT_COMP_LT | AT_COMP_GT | AT_COMP_LTE | AT_COMP_GTE ) ^ shift
+          {
+          set27=input.LT(1);
+          set27=input.LT(1);
+          if ( (input.LA(1) >= AT_COMP_GT && input.LA(1) <= AT_COMP_LTE) ) {
+            input.consume();
+            root_0 = (CommonTree)adaptor.becomeRoot((CommonTree)adaptor.create(set27), root_0);
+            state.errorRecovery=false;
+          }
+          else {
+            MismatchedSetException mse = new MismatchedSetException(null,input);
+            throw mse;
+          }
+          pushFollow(FOLLOW_shift_in_relational964);
+          shift28=shift();
+          state._fsp--;
+
+          adaptor.addChild(root_0, shift28.getTree());
+
+          }
+          break;
+
+        default :
+          break loop8;
+        }
+      }
+
+      }
+
+      retval.stop = input.LT(-1);
+
+      retval.tree = (CommonTree)adaptor.rulePostProcessing(root_0);
+      adaptor.setTokenBoundaries(retval.tree, retval.start, retval.stop);
+
+    }
+    catch (RecognitionException re) {
+      reportError(re);
+      recover(input,re);
+      retval.tree = (CommonTree)adaptor.errorNode(input, retval.start, input.LT(-1), re);
+    }
+    finally {
+      // do for sure before leaving
+    }
+    return retval;
+  }
+  // $ANTLR end "relational"
+
+
+  public static class shift_return extends ParserRuleReturnScope {
+    CommonTree tree;
+    @Override
+    public CommonTree getTree() { return tree; }
+  };
+
+
+  // $ANTLR start "shift"
+  // src/java/org/apache/lucene/expressions/js/Javascript.g:286:1: shift : additive ( ( AT_BIT_SHL | AT_BIT_SHR | AT_BIT_SHU ) ^ additive )* ;
+  public final JavascriptParser.shift_return shift() throws RecognitionException {
+    JavascriptParser.shift_return retval = new JavascriptParser.shift_return();
+    retval.start = input.LT(1);
+
+    CommonTree root_0 = null;
+
+    Token set30=null;
+    ParserRuleReturnScope additive29 =null;
+    ParserRuleReturnScope additive31 =null;
+
+    CommonTree set30_tree=null;
+
+    try {
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:287:5: ( additive ( ( AT_BIT_SHL | AT_BIT_SHR | AT_BIT_SHU ) ^ additive )* )
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:287:7: additive ( ( AT_BIT_SHL | AT_BIT_SHR | AT_BIT_SHU ) ^ additive )*
+      {
+      root_0 = (CommonTree)adaptor.nil();
+
+
+      pushFollow(FOLLOW_additive_in_shift983);
+      additive29=additive();
+      state._fsp--;
+
+      adaptor.addChild(root_0, additive29.getTree());
+
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:287:16: ( ( AT_BIT_SHL | AT_BIT_SHR | AT_BIT_SHU ) ^ additive )*
+      loop9:
+      while (true) {
+        int alt9=2;
+        int LA9_0 = input.LA(1);
+        if ( ((LA9_0 >= AT_BIT_SHL && LA9_0 <= AT_BIT_SHU)) ) {
+          alt9=1;
+        }
+
+        switch (alt9) {
+        case 1 :
+          // src/java/org/apache/lucene/expressions/js/Javascript.g:287:17: ( AT_BIT_SHL | AT_BIT_SHR | AT_BIT_SHU ) ^ additive
+          {
+          set30=input.LT(1);
+          set30=input.LT(1);
+          if ( (input.LA(1) >= AT_BIT_SHL && input.LA(1) <= AT_BIT_SHU) ) {
+            input.consume();
+            root_0 = (CommonTree)adaptor.becomeRoot((CommonTree)adaptor.create(set30), root_0);
+            state.errorRecovery=false;
+          }
+          else {
+            MismatchedSetException mse = new MismatchedSetException(null,input);
+            throw mse;
+          }
+          pushFollow(FOLLOW_additive_in_shift999);
+          additive31=additive();
+          state._fsp--;
+
+          adaptor.addChild(root_0, additive31.getTree());
+
+          }
+          break;
+
+        default :
+          break loop9;
+        }
+      }
+
+      }
+
+      retval.stop = input.LT(-1);
+
+      retval.tree = (CommonTree)adaptor.rulePostProcessing(root_0);
+      adaptor.setTokenBoundaries(retval.tree, retval.start, retval.stop);
+
+    }
+    catch (RecognitionException re) {
+      reportError(re);
+      recover(input,re);
+      retval.tree = (CommonTree)adaptor.errorNode(input, retval.start, input.LT(-1), re);
+    }
+    finally {
+      // do for sure before leaving
+    }
+    return retval;
+  }
+  // $ANTLR end "shift"
+
+
+  public static class additive_return extends ParserRuleReturnScope {
+    CommonTree tree;
+    @Override
+    public CommonTree getTree() { return tree; }
+  };
+
+
+  // $ANTLR start "additive"
+  // src/java/org/apache/lucene/expressions/js/Javascript.g:290:1: additive : multiplicative ( ( AT_ADD | AT_SUBTRACT ) ^ multiplicative )* ;
+  public final JavascriptParser.additive_return additive() throws RecognitionException {
+    JavascriptParser.additive_return retval = new JavascriptParser.additive_return();
+    retval.start = input.LT(1);
+
+    CommonTree root_0 = null;
+
+    Token set33=null;
+    ParserRuleReturnScope multiplicative32 =null;
+    ParserRuleReturnScope multiplicative34 =null;
+
+    CommonTree set33_tree=null;
+
+    try {
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:291:5: ( multiplicative ( ( AT_ADD | AT_SUBTRACT ) ^ multiplicative )* )
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:291:7: multiplicative ( ( AT_ADD | AT_SUBTRACT ) ^ multiplicative )*
+      {
+      root_0 = (CommonTree)adaptor.nil();
+
+
+      pushFollow(FOLLOW_multiplicative_in_additive1018);
+      multiplicative32=multiplicative();
+      state._fsp--;
+
+      adaptor.addChild(root_0, multiplicative32.getTree());
+
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:291:22: ( ( AT_ADD | AT_SUBTRACT ) ^ multiplicative )*
+      loop10:
+      while (true) {
+        int alt10=2;
+        int LA10_0 = input.LA(1);
+        if ( (LA10_0==AT_ADD||LA10_0==AT_SUBTRACT) ) {
+          alt10=1;
+        }
+
+        switch (alt10) {
+        case 1 :
+          // src/java/org/apache/lucene/expressions/js/Javascript.g:291:23: ( AT_ADD | AT_SUBTRACT ) ^ multiplicative
+          {
+          set33=input.LT(1);
+          set33=input.LT(1);
+          if ( input.LA(1)==AT_ADD||input.LA(1)==AT_SUBTRACT ) {
+            input.consume();
+            root_0 = (CommonTree)adaptor.becomeRoot((CommonTree)adaptor.create(set33), root_0);
+            state.errorRecovery=false;
+          }
+          else {
+            MismatchedSetException mse = new MismatchedSetException(null,input);
+            throw mse;
+          }
+          pushFollow(FOLLOW_multiplicative_in_additive1030);
+          multiplicative34=multiplicative();
+          state._fsp--;
+
+          adaptor.addChild(root_0, multiplicative34.getTree());
+
+          }
+          break;
+
+        default :
+          break loop10;
+        }
+      }
+
+      }
+
+      retval.stop = input.LT(-1);
+
+      retval.tree = (CommonTree)adaptor.rulePostProcessing(root_0);
+      adaptor.setTokenBoundaries(retval.tree, retval.start, retval.stop);
+
+    }
+    catch (RecognitionException re) {
+      reportError(re);
+      recover(input,re);
+      retval.tree = (CommonTree)adaptor.errorNode(input, retval.start, input.LT(-1), re);
+    }
+    finally {
+      // do for sure before leaving
+    }
+    return retval;
+  }
+  // $ANTLR end "additive"
+
+
+  public static class multiplicative_return extends ParserRuleReturnScope {
+    CommonTree tree;
+    @Override
+    public CommonTree getTree() { return tree; }
+  };
+
+
+  // $ANTLR start "multiplicative"
+  // src/java/org/apache/lucene/expressions/js/Javascript.g:294:1: multiplicative : unary ( ( AT_MULTIPLY | AT_DIVIDE | AT_MODULO ) ^ unary )* ;
+  public final JavascriptParser.multiplicative_return multiplicative() throws RecognitionException {
+    JavascriptParser.multiplicative_return retval = new JavascriptParser.multiplicative_return();
+    retval.start = input.LT(1);
+
+    CommonTree root_0 = null;
+
+    Token set36=null;
+    ParserRuleReturnScope unary35 =null;
+    ParserRuleReturnScope unary37 =null;
+
+    CommonTree set36_tree=null;
+
+    try {
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:295:5: ( unary ( ( AT_MULTIPLY | AT_DIVIDE | AT_MODULO ) ^ unary )* )
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:295:7: unary ( ( AT_MULTIPLY | AT_DIVIDE | AT_MODULO ) ^ unary )*
+      {
+      root_0 = (CommonTree)adaptor.nil();
+
+
+      pushFollow(FOLLOW_unary_in_multiplicative1049);
+      unary35=unary();
+      state._fsp--;
+
+      adaptor.addChild(root_0, unary35.getTree());
+
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:295:13: ( ( AT_MULTIPLY | AT_DIVIDE | AT_MODULO ) ^ unary )*
+      loop11:
+      while (true) {
+        int alt11=2;
+        int LA11_0 = input.LA(1);
+        if ( (LA11_0==AT_DIVIDE||(LA11_0 >= AT_MODULO && LA11_0 <= AT_MULTIPLY)) ) {
+          alt11=1;
+        }
+
+        switch (alt11) {
+        case 1 :
+          // src/java/org/apache/lucene/expressions/js/Javascript.g:295:14: ( AT_MULTIPLY | AT_DIVIDE | AT_MODULO ) ^ unary
+          {
+          set36=input.LT(1);
+          set36=input.LT(1);
+          if ( input.LA(1)==AT_DIVIDE||(input.LA(1) >= AT_MODULO && input.LA(1) <= AT_MULTIPLY) ) {
+            input.consume();
+            root_0 = (CommonTree)adaptor.becomeRoot((CommonTree)adaptor.create(set36), root_0);
+            state.errorRecovery=false;
+          }
+          else {
+            MismatchedSetException mse = new MismatchedSetException(null,input);
+            throw mse;
+          }
+          pushFollow(FOLLOW_unary_in_multiplicative1065);
+          unary37=unary();
+          state._fsp--;
+
+          adaptor.addChild(root_0, unary37.getTree());
+
+          }
+          break;
+
+        default :
+          break loop11;
+        }
+      }
+
+      }
+
+      retval.stop = input.LT(-1);
+
+      retval.tree = (CommonTree)adaptor.rulePostProcessing(root_0);
+      adaptor.setTokenBoundaries(retval.tree, retval.start, retval.stop);
+
+    }
+    catch (RecognitionException re) {
+      reportError(re);
+      recover(input,re);
+      retval.tree = (CommonTree)adaptor.errorNode(input, retval.start, input.LT(-1), re);
+    }
+    finally {
+      // do for sure before leaving
+    }
+    return retval;
+  }
+  // $ANTLR end "multiplicative"
+
+
+  public static class unary_return extends ParserRuleReturnScope {
+    CommonTree tree;
+    @Override
+    public CommonTree getTree() { return tree; }
+  };
+
+
+  // $ANTLR start "unary"
+  // src/java/org/apache/lucene/expressions/js/Javascript.g:298:1: unary : ( postfix | AT_ADD ! unary | unary_operator ^ unary );
+  public final JavascriptParser.unary_return unary() throws RecognitionException {
+    JavascriptParser.unary_return retval = new JavascriptParser.unary_return();
+    retval.start = input.LT(1);
+
+    CommonTree root_0 = null;
+
+    Token AT_ADD39=null;
+    ParserRuleReturnScope postfix38 =null;
+    ParserRuleReturnScope unary40 =null;
+    ParserRuleReturnScope unary_operator41 =null;
+    ParserRuleReturnScope unary42 =null;
+
+    CommonTree AT_ADD39_tree=null;
+
+    try {
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:299:5: ( postfix | AT_ADD ! unary | unary_operator ^ unary )
+      int alt12=3;
+      switch ( input.LA(1) ) {
+      case AT_LPAREN:
+      case DECIMAL:
+      case HEX:
+      case ID:
+      case OCTAL:
+        {
+        alt12=1;
+        }
+        break;
+      case AT_ADD:
+        {
+        alt12=2;
+        }
+        break;
+      case AT_BIT_NOT:
+      case AT_BOOL_NOT:
+      case AT_SUBTRACT:
+        {
+        alt12=3;
+        }
+        break;
+      default:
+        NoViableAltException nvae =
+          new NoViableAltException("", 12, 0, input);
+        throw nvae;
+      }
+      switch (alt12) {
+        case 1 :
+          // src/java/org/apache/lucene/expressions/js/Javascript.g:299:7: postfix
+          {
+          root_0 = (CommonTree)adaptor.nil();
+
+
+          pushFollow(FOLLOW_postfix_in_unary1084);
+          postfix38=postfix();
+          state._fsp--;
+
+          adaptor.addChild(root_0, postfix38.getTree());
+
+          }
+          break;
+        case 2 :
+          // src/java/org/apache/lucene/expressions/js/Javascript.g:300:7: AT_ADD ! unary
+          {
+          root_0 = (CommonTree)adaptor.nil();
+
+
+          AT_ADD39=(Token)match(input,AT_ADD,FOLLOW_AT_ADD_in_unary1092); 
+          pushFollow(FOLLOW_unary_in_unary1095);
+          unary40=unary();
+          state._fsp--;
+
+          adaptor.addChild(root_0, unary40.getTree());
+
+          }
+          break;
+        case 3 :
+          // src/java/org/apache/lucene/expressions/js/Javascript.g:301:7: unary_operator ^ unary
+          {
+          root_0 = (CommonTree)adaptor.nil();
+
+
+          pushFollow(FOLLOW_unary_operator_in_unary1103);
+          unary_operator41=unary_operator();
+          state._fsp--;
+
+          root_0 = (CommonTree)adaptor.becomeRoot(unary_operator41.getTree(), root_0);
+          pushFollow(FOLLOW_unary_in_unary1106);
+          unary42=unary();
+          state._fsp--;
+
+          adaptor.addChild(root_0, unary42.getTree());
+
+          }
+          break;
+
+      }
+      retval.stop = input.LT(-1);
+
+      retval.tree = (CommonTree)adaptor.rulePostProcessing(root_0);
+      adaptor.setTokenBoundaries(retval.tree, retval.start, retval.stop);
+
+    }
+    catch (RecognitionException re) {
+      reportError(re);
+      recover(input,re);
+      retval.tree = (CommonTree)adaptor.errorNode(input, retval.start, input.LT(-1), re);
+    }
+    finally {
+      // do for sure before leaving
+    }
+    return retval;
+  }
+  // $ANTLR end "unary"
+
+
+  public static class unary_operator_return extends ParserRuleReturnScope {
+    CommonTree tree;
+    @Override
+    public CommonTree getTree() { return tree; }
+  };
+
+
+  // $ANTLR start "unary_operator"
+  // src/java/org/apache/lucene/expressions/js/Javascript.g:304:1: unary_operator : ( AT_SUBTRACT -> AT_NEGATE | AT_BIT_NOT | AT_BOOL_NOT );
+  public final JavascriptParser.unary_operator_return unary_operator() throws RecognitionException {
+    JavascriptParser.unary_operator_return retval = new JavascriptParser.unary_operator_return();
+    retval.start = input.LT(1);
+
+    CommonTree root_0 = null;
+
+    Token AT_SUBTRACT43=null;
+    Token AT_BIT_NOT44=null;
+    Token AT_BOOL_NOT45=null;
+
+    CommonTree AT_SUBTRACT43_tree=null;
+    CommonTree AT_BIT_NOT44_tree=null;
+    CommonTree AT_BOOL_NOT45_tree=null;
+    RewriteRuleTokenStream stream_AT_SUBTRACT=new RewriteRuleTokenStream(adaptor,"token AT_SUBTRACT");
+
+    try {
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:305:5: ( AT_SUBTRACT -> AT_NEGATE | AT_BIT_NOT | AT_BOOL_NOT )
+      int alt13=3;
+      switch ( input.LA(1) ) {
+      case AT_SUBTRACT:
+        {
+        alt13=1;
+        }
+        break;
+      case AT_BIT_NOT:
+        {
+        alt13=2;
+        }
+        break;
+      case AT_BOOL_NOT:
+        {
+        alt13=3;
+        }
+        break;
+      default:
+        NoViableAltException nvae =
+          new NoViableAltException("", 13, 0, input);
+        throw nvae;
+      }
+      switch (alt13) {
+        case 1 :
+          // src/java/org/apache/lucene/expressions/js/Javascript.g:305:7: AT_SUBTRACT
+          {
+          AT_SUBTRACT43=(Token)match(input,AT_SUBTRACT,FOLLOW_AT_SUBTRACT_in_unary_operator1123);  
+          stream_AT_SUBTRACT.add(AT_SUBTRACT43);
+
+          // AST REWRITE
+          // elements: 
+          // token labels: 
+          // rule labels: retval
+          // token list labels: 
+          // rule list labels: 
+          // wildcard labels: 
+          retval.tree = root_0;
+          RewriteRuleSubtreeStream stream_retval=new RewriteRuleSubtreeStream(adaptor,"rule retval",retval!=null?retval.getTree():null);
+
+          root_0 = (CommonTree)adaptor.nil();
+          // 305:19: -> AT_NEGATE
+          {
+            adaptor.addChild(root_0, (CommonTree)adaptor.create(AT_NEGATE, "AT_NEGATE"));
+          }
+
+
+          retval.tree = root_0;
+
+          }
+          break;
+        case 2 :
+          // src/java/org/apache/lucene/expressions/js/Javascript.g:306:7: AT_BIT_NOT
+          {
+          root_0 = (CommonTree)adaptor.nil();
+
+
+          AT_BIT_NOT44=(Token)match(input,AT_BIT_NOT,FOLLOW_AT_BIT_NOT_in_unary_operator1135); 
+          AT_BIT_NOT44_tree = (CommonTree)adaptor.create(AT_BIT_NOT44);
+          adaptor.addChild(root_0, AT_BIT_NOT44_tree);
+
+          }
+          break;
+        case 3 :
+          // src/java/org/apache/lucene/expressions/js/Javascript.g:307:7: AT_BOOL_NOT
+          {
+          root_0 = (CommonTree)adaptor.nil();
+
+
+          AT_BOOL_NOT45=(Token)match(input,AT_BOOL_NOT,FOLLOW_AT_BOOL_NOT_in_unary_operator1143); 
+          AT_BOOL_NOT45_tree = (CommonTree)adaptor.create(AT_BOOL_NOT45);
+          adaptor.addChild(root_0, AT_BOOL_NOT45_tree);
+
+          }
+          break;
+
+      }
+      retval.stop = input.LT(-1);
+
+      retval.tree = (CommonTree)adaptor.rulePostProcessing(root_0);
+      adaptor.setTokenBoundaries(retval.tree, retval.start, retval.stop);
+
+    }
+    catch (RecognitionException re) {
+      reportError(re);
+      recover(input,re);
+      retval.tree = (CommonTree)adaptor.errorNode(input, retval.start, input.LT(-1), re);
+    }
+    finally {
+      // do for sure before leaving
+    }
+    return retval;
+  }
+  // $ANTLR end "unary_operator"
+
+
+  public static class postfix_return extends ParserRuleReturnScope {
+    CommonTree tree;
+    @Override
+    public CommonTree getTree() { return tree; }
+  };
+
+
+  // $ANTLR start "postfix"
+  // src/java/org/apache/lucene/expressions/js/Javascript.g:310:1: postfix : ( primary | ID arguments -> ^( AT_CALL ID ( arguments )? ) );
+  public final JavascriptParser.postfix_return postfix() throws RecognitionException {
+    JavascriptParser.postfix_return retval = new JavascriptParser.postfix_return();
+    retval.start = input.LT(1);
+
+    CommonTree root_0 = null;
+
+    Token ID47=null;
+    ParserRuleReturnScope primary46 =null;
+    ParserRuleReturnScope arguments48 =null;
+
+    CommonTree ID47_tree=null;
+    RewriteRuleTokenStream stream_ID=new RewriteRuleTokenStream(adaptor,"token ID");
+    RewriteRuleSubtreeStream stream_arguments=new RewriteRuleSubtreeStream(adaptor,"rule arguments");
+
+    try {
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:311:5: ( primary | ID arguments -> ^( AT_CALL ID ( arguments )? ) )
+      int alt14=2;
+      int LA14_0 = input.LA(1);
+      if ( (LA14_0==ID) ) {
+        int LA14_1 = input.LA(2);
+        if ( (LA14_1==EOF||(LA14_1 >= AT_ADD && LA14_1 <= AT_BIT_AND)||(LA14_1 >= AT_BIT_OR && LA14_1 <= AT_BOOL_AND)||LA14_1==AT_BOOL_OR||(LA14_1 >= AT_COLON && LA14_1 <= AT_DIVIDE)||(LA14_1 >= AT_MODULO && LA14_1 <= AT_MULTIPLY)||(LA14_1 >= AT_RPAREN && LA14_1 <= AT_SUBTRACT)) ) {
+          alt14=1;
+        }
+        else if ( (LA14_1==AT_LPAREN) ) {
+          alt14=2;
+        }
+
+        else {
+          int nvaeMark = input.mark();
+          try {
+            input.consume();
+            NoViableAltException nvae =
+              new NoViableAltException("", 14, 1, input);
+            throw nvae;
+          } finally {
+            input.rewind(nvaeMark);
+          }
+        }
+
+      }
+      else if ( (LA14_0==AT_LPAREN||LA14_0==DECIMAL||LA14_0==HEX||LA14_0==OCTAL) ) {
+        alt14=1;
+      }
+
+      else {
+        NoViableAltException nvae =
+          new NoViableAltException("", 14, 0, input);
+        throw nvae;
+      }
+
+      switch (alt14) {
+        case 1 :
+          // src/java/org/apache/lucene/expressions/js/Javascript.g:311:7: primary
+          {
+          root_0 = (CommonTree)adaptor.nil();
+
+
+          pushFollow(FOLLOW_primary_in_postfix1160);
+          primary46=primary();
+          state._fsp--;
+
+          adaptor.addChild(root_0, primary46.getTree());
+
+          }
+          break;
+        case 2 :
+          // src/java/org/apache/lucene/expressions/js/Javascript.g:312:7: ID arguments
+          {
+          ID47=(Token)match(input,ID,FOLLOW_ID_in_postfix1168);  
+          stream_ID.add(ID47);
+
+          pushFollow(FOLLOW_arguments_in_postfix1170);
+          arguments48=arguments();
+          state._fsp--;
+
+          stream_arguments.add(arguments48.getTree());
+          // AST REWRITE
+          // elements: arguments, ID
+          // token labels: 
+          // rule labels: retval
+          // token list labels: 
+          // rule list labels: 
+          // wildcard labels: 
+          retval.tree = root_0;
+          RewriteRuleSubtreeStream stream_retval=new RewriteRuleSubtreeStream(adaptor,"rule retval",retval!=null?retval.getTree():null);
+
+          root_0 = (CommonTree)adaptor.nil();
+          // 312:20: -> ^( AT_CALL ID ( arguments )? )
+          {
+            // src/java/org/apache/lucene/expressions/js/Javascript.g:312:23: ^( AT_CALL ID ( arguments )? )
+            {
+            CommonTree root_1 = (CommonTree)adaptor.nil();
+            root_1 = (CommonTree)adaptor.becomeRoot((CommonTree)adaptor.create(AT_CALL, "AT_CALL"), root_1);
+            adaptor.addChild(root_1, stream_ID.nextNode());
+            // src/java/org/apache/lucene/expressions/js/Javascript.g:312:36: ( arguments )?
+            if ( stream_arguments.hasNext() ) {
+              adaptor.addChild(root_1, stream_arguments.nextTree());
+            }
+            stream_arguments.reset();
+
+            adaptor.addChild(root_0, root_1);
+            }
+
+          }
+
+
+          retval.tree = root_0;
+
+          }
+          break;
+
+      }
+      retval.stop = input.LT(-1);
+
+      retval.tree = (CommonTree)adaptor.rulePostProcessing(root_0);
+      adaptor.setTokenBoundaries(retval.tree, retval.start, retval.stop);
+
+    }
+    catch (RecognitionException re) {
+      reportError(re);
+      recover(input,re);
+      retval.tree = (CommonTree)adaptor.errorNode(input, retval.start, input.LT(-1), re);
+    }
+    finally {
+      // do for sure before leaving
+    }
+    return retval;
+  }
+  // $ANTLR end "postfix"
+
+
+  public static class primary_return extends ParserRuleReturnScope {
+    CommonTree tree;
+    @Override
+    public CommonTree getTree() { return tree; }
+  };
+
+
+  // $ANTLR start "primary"
+  // src/java/org/apache/lucene/expressions/js/Javascript.g:315:1: primary : ( ID | numeric | AT_LPAREN ! conditional AT_RPAREN !);
+  public final JavascriptParser.primary_return primary() throws RecognitionException {
+    JavascriptParser.primary_return retval = new JavascriptParser.primary_return();
+    retval.start = input.LT(1);
+
+    CommonTree root_0 = null;
+
+    Token ID49=null;
+    Token AT_LPAREN51=null;
+    Token AT_RPAREN53=null;
+    ParserRuleReturnScope numeric50 =null;
+    ParserRuleReturnScope conditional52 =null;
+
+    CommonTree ID49_tree=null;
+    CommonTree AT_LPAREN51_tree=null;
+    CommonTree AT_RPAREN53_tree=null;
+
+    try {
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:316:5: ( ID | numeric | AT_LPAREN ! conditional AT_RPAREN !)
+      int alt15=3;
+      switch ( input.LA(1) ) {
+      case ID:
+        {
+        alt15=1;
+        }
+        break;
+      case DECIMAL:
+      case HEX:
+      case OCTAL:
+        {
+        alt15=2;
+        }
+        break;
+      case AT_LPAREN:
+        {
+        alt15=3;
+        }
+        break;
+      default:
+        NoViableAltException nvae =
+          new NoViableAltException("", 15, 0, input);
+        throw nvae;
+      }
+      switch (alt15) {
+        case 1 :
+          // src/java/org/apache/lucene/expressions/js/Javascript.g:316:7: ID
+          {
+          root_0 = (CommonTree)adaptor.nil();
+
+
+          ID49=(Token)match(input,ID,FOLLOW_ID_in_primary1198); 
+          ID49_tree = (CommonTree)adaptor.create(ID49);
+          adaptor.addChild(root_0, ID49_tree);
+
+          }
+          break;
+        case 2 :
+          // src/java/org/apache/lucene/expressions/js/Javascript.g:317:7: numeric
+          {
+          root_0 = (CommonTree)adaptor.nil();
+
+
+          pushFollow(FOLLOW_numeric_in_primary1206);
+          numeric50=numeric();
+          state._fsp--;
+
+          adaptor.addChild(root_0, numeric50.getTree());
+
+          }
+          break;
+        case 3 :
+          // src/java/org/apache/lucene/expressions/js/Javascript.g:318:7: AT_LPAREN ! conditional AT_RPAREN !
+          {
+          root_0 = (CommonTree)adaptor.nil();
+
+
+          AT_LPAREN51=(Token)match(input,AT_LPAREN,FOLLOW_AT_LPAREN_in_primary1214); 
+          pushFollow(FOLLOW_conditional_in_primary1217);
+          conditional52=conditional();
+          state._fsp--;
+
+          adaptor.addChild(root_0, conditional52.getTree());
+
+          AT_RPAREN53=(Token)match(input,AT_RPAREN,FOLLOW_AT_RPAREN_in_primary1219); 
+          }
+          break;
+
+      }
+      retval.stop = input.LT(-1);
+
+      retval.tree = (CommonTree)adaptor.rulePostProcessing(root_0);
+      adaptor.setTokenBoundaries(retval.tree, retval.start, retval.stop);
+
+    }
+    catch (RecognitionException re) {
+      reportError(re);
+      recover(input,re);
+      retval.tree = (CommonTree)adaptor.errorNode(input, retval.start, input.LT(-1), re);
+    }
+    finally {
+      // do for sure before leaving
+    }
+    return retval;
+  }
+  // $ANTLR end "primary"
+
+
+  public static class arguments_return extends ParserRuleReturnScope {
+    CommonTree tree;
+    @Override
+    public CommonTree getTree() { return tree; }
+  };
+
+
+  // $ANTLR start "arguments"
+  // src/java/org/apache/lucene/expressions/js/Javascript.g:321:1: arguments : AT_LPAREN ! ( conditional ( AT_COMMA ! conditional )* )? AT_RPAREN !;
+  public final JavascriptParser.arguments_return arguments() throws RecognitionException {
+    JavascriptParser.arguments_return retval = new JavascriptParser.arguments_return();
+    retval.start = input.LT(1);
+
+    CommonTree root_0 = null;
+
+    Token AT_LPAREN54=null;
+    Token AT_COMMA56=null;
+    Token AT_RPAREN58=null;
+    ParserRuleReturnScope conditional55 =null;
+    ParserRuleReturnScope conditional57 =null;
+
+    CommonTree AT_LPAREN54_tree=null;
+    CommonTree AT_COMMA56_tree=null;
+    CommonTree AT_RPAREN58_tree=null;
+
+    try {
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:322:5: ( AT_LPAREN ! ( conditional ( AT_COMMA ! conditional )* )? AT_RPAREN !)
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:322:7: AT_LPAREN ! ( conditional ( AT_COMMA ! conditional )* )? AT_RPAREN !
+      {
+      root_0 = (CommonTree)adaptor.nil();
+
+
+      AT_LPAREN54=(Token)match(input,AT_LPAREN,FOLLOW_AT_LPAREN_in_arguments1237); 
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:322:18: ( conditional ( AT_COMMA ! conditional )* )?
+      int alt17=2;
+      int LA17_0 = input.LA(1);
+      if ( (LA17_0==AT_ADD||LA17_0==AT_BIT_NOT||LA17_0==AT_BOOL_NOT||LA17_0==AT_LPAREN||(LA17_0 >= AT_SUBTRACT && LA17_0 <= DECIMAL)||LA17_0==HEX||(LA17_0 >= ID && LA17_0 <= OCTAL)) ) {
+        alt17=1;
+      }
+      switch (alt17) {
+        case 1 :
+          // src/java/org/apache/lucene/expressions/js/Javascript.g:322:19: conditional ( AT_COMMA ! conditional )*
+          {
+          pushFollow(FOLLOW_conditional_in_arguments1241);
+          conditional55=conditional();
+          state._fsp--;
+
+          adaptor.addChild(root_0, conditional55.getTree());
+
+          // src/java/org/apache/lucene/expressions/js/Javascript.g:322:31: ( AT_COMMA ! conditional )*
+          loop16:
+          while (true) {
+            int alt16=2;
+            int LA16_0 = input.LA(1);
+            if ( (LA16_0==AT_COMMA) ) {
+              alt16=1;
+            }
+
+            switch (alt16) {
+            case 1 :
+              // src/java/org/apache/lucene/expressions/js/Javascript.g:322:32: AT_COMMA ! conditional
+              {
+              AT_COMMA56=(Token)match(input,AT_COMMA,FOLLOW_AT_COMMA_in_arguments1244); 
+              pushFollow(FOLLOW_conditional_in_arguments1247);
+              conditional57=conditional();
+              state._fsp--;
+
+              adaptor.addChild(root_0, conditional57.getTree());
+
+              }
+              break;
+
+            default :
+              break loop16;
+            }
+          }
+
+          }
+          break;
+
+      }
+
+      AT_RPAREN58=(Token)match(input,AT_RPAREN,FOLLOW_AT_RPAREN_in_arguments1253); 
+      }
+
+      retval.stop = input.LT(-1);
+
+      retval.tree = (CommonTree)adaptor.rulePostProcessing(root_0);
+      adaptor.setTokenBoundaries(retval.tree, retval.start, retval.stop);
+
+    }
+    catch (RecognitionException re) {
+      reportError(re);
+      recover(input,re);
+      retval.tree = (CommonTree)adaptor.errorNode(input, retval.start, input.LT(-1), re);
+    }
+    finally {
+      // do for sure before leaving
+    }
+    return retval;
+  }
+  // $ANTLR end "arguments"
+
+
+  public static class numeric_return extends ParserRuleReturnScope {
+    CommonTree tree;
+    @Override
+    public CommonTree getTree() { return tree; }
+  };
+
+
+  // $ANTLR start "numeric"
+  // src/java/org/apache/lucene/expressions/js/Javascript.g:325:1: numeric : ( HEX | OCTAL | DECIMAL );
+  public final JavascriptParser.numeric_return numeric() throws RecognitionException {
+    JavascriptParser.numeric_return retval = new JavascriptParser.numeric_return();
+    retval.start = input.LT(1);
+
+    CommonTree root_0 = null;
+
+    Token set59=null;
+
+    CommonTree set59_tree=null;
+
+    try {
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:326:5: ( HEX | OCTAL | DECIMAL )
+      // src/java/org/apache/lucene/expressions/js/Javascript.g:
+      {
+      root_0 = (CommonTree)adaptor.nil();
+
+
+      set59=input.LT(1);
+      if ( input.LA(1)==DECIMAL||input.LA(1)==HEX||input.LA(1)==OCTAL ) {
+        input.consume();
+        adaptor.addChild(root_0, (CommonTree)adaptor.create(set59));
+        state.errorRecovery=false;
+      }
+      else {
+        MismatchedSetException mse = new MismatchedSetException(null,input);
+        throw mse;
+      }
+      }
+
+      retval.stop = input.LT(-1);
+
+      retval.tree = (CommonTree)adaptor.rulePostProcessing(root_0);
+      adaptor.setTokenBoundaries(retval.tree, retval.start, retval.stop);
+
+    }
+    catch (RecognitionException re) {
+      reportError(re);
+      recover(input,re);
+      retval.tree = (CommonTree)adaptor.errorNode(input, retval.start, input.LT(-1), re);
+    }
+    finally {
+      // do for sure before leaving
+    }
+    return retval;
+  }
+  // $ANTLR end "numeric"
+
+  // Delegated rules
+
+
+
+  public static final BitSet FOLLOW_conditional_in_expression737 = new BitSet(new long[]{0x0000000000000000L});
+  public static final BitSet FOLLOW_EOF_in_expression739 = new BitSet(new long[]{0x0000000000000002L});
+  public static final BitSet FOLLOW_logical_or_in_conditional757 = new BitSet(new long[]{0x0000000001000002L});
+  public static final BitSet FOLLOW_AT_COND_QUE_in_conditional760 = new BitSet(new long[]{0x000001A308002050L});
+  public static final BitSet FOLLOW_conditional_in_conditional763 = new BitSet(new long[]{0x0000000000010000L});
+  public static final BitSet FOLLOW_AT_COLON_in_conditional765 = new BitSet(new long[]{0x000001A308002050L});
+  public static final BitSet FOLLOW_conditional_in_conditional768 = new BitSet(new long[]{0x0000000000000002L});
+  public static final BitSet FOLLOW_logical_and_in_logical_or787 = new BitSet(new long[]{0x0000000000004002L});
+  public static final BitSet FOLLOW_AT_BOOL_OR_in_logical_or790 = new BitSet(new long[]{0x000001A308002050L});
+  public static final BitSet FOLLOW_logical_and_in_logical_or793 = new BitSet(new long[]{0x0000000000004002L});
+  public static final BitSet FOLLOW_bitwise_or_in_logical_and812 = new BitSet(new long[]{0x0000000000001002L});
+  public static final BitSet FOLLOW_AT_BOOL_AND_in_logical_and815 = new BitSet(new long[]{0x000001A308002050L});
+  public static final BitSet FOLLOW_bitwise_or_in_logical_and818 = new BitSet(new long[]{0x0000000000001002L});
+  public static final BitSet FOLLOW_bitwise_xor_in_bitwise_or837 = new BitSet(new long[]{0x0000000000000082L});
+  public static final BitSet FOLLOW_AT_BIT_OR_in_bitwise_or840 = new BitSet(new long[]{0x000001A308002050L});
+  public static final BitSet FOLLOW_bitwise_xor_in_bitwise_or843 = new BitSet(new long[]{0x0000000000000082L});
+  public static final BitSet FOLLOW_bitwise_and_in_bitwise_xor862 = new BitSet(new long[]{0x0000000000000802L});
+  public static final BitSet FOLLOW_AT_BIT_XOR_in_bitwise_xor865 = new BitSet(new long[]{0x000001A308002050L});
+  public static final BitSet FOLLOW_bitwise_and_in_bitwise_xor868 = new BitSet(new long[]{0x0000000000000802L});
+  public static final BitSet FOLLOW_equality_in_bitwise_and888 = new BitSet(new long[]{0x0000000000000022L});
+  public static final BitSet FOLLOW_AT_BIT_AND_in_bitwise_and891 = new BitSet(new long[]{0x000001A308002050L});
+  public static final BitSet FOLLOW_equality_in_bitwise_and894 = new BitSet(new long[]{0x0000000000000022L});
+  public static final BitSet FOLLOW_relational_in_equality913 = new BitSet(new long[]{0x0000000000840002L});
+  public static final BitSet FOLLOW_set_in_equality916 = new BitSet(new long[]{0x000001A308002050L});
+  public static final BitSet FOLLOW_relational_in_equality925 = new BitSet(new long[]{0x0000000000840002L});
+  public static final BitSet FOLLOW_shift_in_relational944 = new BitSet(new long[]{0x0000000000780002L});
+  public static final BitSet FOLLOW_set_in_relational947 = new BitSet(new long[]{0x000001A308002050L});
+  public static final BitSet FOLLOW_shift_in_relational964 = new BitSet(new long[]{0x0000000000780002L});
+  public static final BitSet FOLLOW_additive_in_shift983 = new BitSet(new long[]{0x0000000000000702L});
+  public static final BitSet FOLLOW_set_in_shift986 = new BitSet(new long[]{0x000001A308002050L});
+  public static final BitSet FOLLOW_additive_in_shift999 = new BitSet(new long[]{0x0000000000000702L});
+  public static final BitSet FOLLOW_multiplicative_in_additive1018 = new BitSet(new long[]{0x0000000100000012L});
+  public static final BitSet FOLLOW_set_in_additive1021 = new BitSet(new long[]{0x000001A308002050L});
+  public static final BitSet FOLLOW_multiplicative_in_additive1030 = new BitSet(new long[]{0x0000000100000012L});
+  public static final BitSet FOLLOW_unary_in_multiplicative1049 = new BitSet(new long[]{0x0000000032000002L});
+  public static final BitSet FOLLOW_set_in_multiplicative1052 = new BitSet(new long[]{0x000001A308002050L});
+  public static final BitSet FOLLOW_unary_in_multiplicative1065 = new BitSet(new long[]{0x0000000032000002L});
+  public static final BitSet FOLLOW_postfix_in_unary1084 = new BitSet(new long[]{0x0000000000000002L});
+  public static final BitSet FOLLOW_AT_ADD_in_unary1092 = new BitSet(new long[]{0x000001A308002050L});
+  public static final BitSet FOLLOW_unary_in_unary1095 = new BitSet(new long[]{0x0000000000000002L});
+  public static final BitSet FOLLOW_unary_operator_in_unary1103 = new BitSet(new long[]{0x000001A308002050L});
+  public static final BitSet FOLLOW_unary_in_unary1106 = new BitSet(new long[]{0x0000000000000002L});
+  public static final BitSet FOLLOW_AT_SUBTRACT_in_unary_operator1123 = new BitSet(new long[]{0x0000000000000002L});
+  public static final BitSet FOLLOW_AT_BIT_NOT_in_unary_operator1135 = new BitSet(new long[]{0x0000000000000002L});
+  public static final BitSet FOLLOW_AT_BOOL_NOT_in_unary_operator1143 = new BitSet(new long[]{0x0000000000000002L});
+  public static final BitSet FOLLOW_primary_in_postfix1160 = new BitSet(new long[]{0x0000000000000002L});
+  public static final BitSet FOLLOW_ID_in_postfix1168 = new BitSet(new long[]{0x0000000008000000L});
+  public static final BitSet FOLLOW_arguments_in_postfix1170 = new BitSet(new long[]{0x0000000000000002L});
+  public static final BitSet FOLLOW_ID_in_primary1198 = new BitSet(new long[]{0x0000000000000002L});
+  public static final BitSet FOLLOW_numeric_in_primary1206 = new BitSet(new long[]{0x0000000000000002L});
+  public static final BitSet FOLLOW_AT_LPAREN_in_primary1214 = new BitSet(new long[]{0x000001A308002050L});
+  public static final BitSet FOLLOW_conditional_in_primary1217 = new BitSet(new long[]{0x0000000080000000L});
+  public static final BitSet FOLLOW_AT_RPAREN_in_primary1219 = new BitSet(new long[]{0x0000000000000002L});
+  public static final BitSet FOLLOW_AT_LPAREN_in_arguments1237 = new BitSet(new long[]{0x000001A388002050L});
+  public static final BitSet FOLLOW_conditional_in_arguments1241 = new BitSet(new long[]{0x0000000080020000L});
+  public static final BitSet FOLLOW_AT_COMMA_in_arguments1244 = new BitSet(new long[]{0x000001A308002050L});
+  public static final BitSet FOLLOW_conditional_in_arguments1247 = new BitSet(new long[]{0x0000000080020000L});
+  public static final BitSet FOLLOW_AT_RPAREN_in_arguments1253 = new BitSet(new long[]{0x0000000000000002L});
+}
diff --git a/lucene/expressions/src/java/org/apache/lucene/expressions/js/package.html b/lucene/expressions/src/java/org/apache/lucene/expressions/js/package.html
new file mode 100644
index 0000000..a8fda2f
--- /dev/null
+++ b/lucene/expressions/src/java/org/apache/lucene/expressions/js/package.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.
+-->
+<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>| &amp; ^ ~ &lt;&lt; &gt;&gt; &gt;&gt;&gt;</code></li>
+<li>Boolean operators (including the ternary operator): <code>&& || ! ?:</code></li>
+<li>Comparison operators: <code>&lt; &lt;= == &gt;= &gt;</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>
\ No newline at end of file
diff --git a/lucene/expressions/src/java/org/apache/lucene/expressions/package.html b/lucene/expressions/src/java/org/apache/lucene/expressions/package.html
new file mode 100644
index 0000000..d4de75d
--- /dev/null
+++ b/lucene/expressions/src/java/org/apache/lucene/expressions/package.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>
\ No newline at end of file
diff --git a/lucene/expressions/src/java/overview.html b/lucene/expressions/src/java/overview.html
new file mode 100644
index 0000000..202cd76
--- /dev/null
+++ b/lucene/expressions/src/java/overview.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>
\ No newline at end of file
diff --git a/lucene/expressions/src/resources/org/apache/lucene/expressions/js/JavascriptCompiler.properties b/lucene/expressions/src/resources/org/apache/lucene/expressions/js/JavascriptCompiler.properties
new file mode 100644
index 0000000..893daae
--- /dev/null
+++ b/lucene/expressions/src/resources/org/apache/lucene/expressions/js/JavascriptCompiler.properties
@@ -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
diff --git a/lucene/expressions/src/test/org/apache/lucene/expressions/TestDemoExpressions.java b/lucene/expressions/src/test/org/apache/lucene/expressions/TestDemoExpressions.java
new file mode 100644
index 0000000..9567154
--- /dev/null
+++ b/lucene/expressions/src/test/org/apache/lucene/expressions/TestDemoExpressions.java
@@ -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));
+    }
+  }
+}
diff --git a/lucene/expressions/src/test/org/apache/lucene/expressions/TestExpressionSorts.java b/lucene/expressions/src/test/org/apache/lucene/expressions/TestExpressionSorts.java
new file mode 100644
index 0000000..0617577
--- /dev/null
+++ b/lucene/expressions/src/test/org/apache/lucene/expressions/TestExpressionSorts.java
@@ -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);
+    }
+  }
+}
+
diff --git a/lucene/expressions/src/test/org/apache/lucene/expressions/TestExpressionValidation.java b/lucene/expressions/src/test/org/apache/lucene/expressions/TestExpressionValidation.java
new file mode 100644
index 0000000..be2a9cb
--- /dev/null
+++ b/lucene/expressions/src/test/org/apache/lucene/expressions/TestExpressionValidation.java
@@ -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"));
+    }
+  }
+}
diff --git a/lucene/expressions/src/test/org/apache/lucene/expressions/js/TestCustomFunctions.java b/lucene/expressions/src/test/org/apache/lucene/expressions/js/TestCustomFunctions.java
new file mode 100644
index 0000000..d9b6ba5
--- /dev/null
+++ b/lucene/expressions/src/test/org/apache/lucene/expressions/js/TestCustomFunctions.java
@@ -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"));
+    }
+  }
+}
diff --git a/lucene/expressions/src/test/org/apache/lucene/expressions/js/TestJavascriptCompiler.java b/lucene/expressions/src/test/org/apache/lucene/expressions/js/TestJavascriptCompiler.java
new file mode 100644
index 0000000..b8944a0
--- /dev/null
+++ b/lucene/expressions/src/test/org/apache/lucene/expressions/js/TestJavascriptCompiler.java
@@ -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"));
+    }
+  }
+}
diff --git a/lucene/expressions/src/test/org/apache/lucene/expressions/js/TestJavascriptFunction.java b/lucene/expressions/src/test/org/apache/lucene/expressions/js/TestJavascriptFunction.java
new file mode 100644
index 0000000..b867808
--- /dev/null
+++ b/lucene/expressions/src/test/org/apache/lucene/expressions/js/TestJavascriptFunction.java
@@ -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);
+  }
+}
diff --git a/lucene/expressions/src/test/org/apache/lucene/expressions/js/TestJavascriptOperations.java b/lucene/expressions/src/test/org/apache/lucene/expressions/js/TestJavascriptOperations.java
new file mode 100644
index 0000000..f0cd482
--- /dev/null
+++ b/lucene/expressions/src/test/org/apache/lucene/expressions/js/TestJavascriptOperations.java
@@ -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);
+
+  }
+}
diff --git a/lucene/licenses/antlr-runtime-3.5.jar.sha1 b/lucene/licenses/antlr-runtime-3.5.jar.sha1
new file mode 100644
index 0000000..d90b777
--- /dev/null
+++ b/lucene/licenses/antlr-runtime-3.5.jar.sha1
@@ -0,0 +1 @@
+0baa82bff19059401e90e1b90020beb9c96305d7
diff --git a/lucene/licenses/antlr-runtime-LICENSE-BSD_LIKE.txt b/lucene/licenses/antlr-runtime-LICENSE-BSD_LIKE.txt
new file mode 100644
index 0000000..a6e3ad0
--- /dev/null
+++ b/lucene/licenses/antlr-runtime-LICENSE-BSD_LIKE.txt
@@ -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.
diff --git a/lucene/licenses/antlr-runtime-NOTICE.txt b/lucene/licenses/antlr-runtime-NOTICE.txt
new file mode 100644
index 0000000..8d1c8b6
--- /dev/null
+++ b/lucene/licenses/antlr-runtime-NOTICE.txt
@@ -0,0 +1 @@
+ 
diff --git a/lucene/licenses/asm-4.1.jar.sha1 b/lucene/licenses/asm-4.1.jar.sha1
new file mode 100644
index 0000000..fca9878
--- /dev/null
+++ b/lucene/licenses/asm-4.1.jar.sha1
@@ -0,0 +1 @@
+ad568238ee36a820bd6c6806807e8a14ea34684d
diff --git a/lucene/licenses/asm-LICENSE-BSD_LIKE.txt b/lucene/licenses/asm-LICENSE-BSD_LIKE.txt
new file mode 100644
index 0000000..afb064f
--- /dev/null
+++ b/lucene/licenses/asm-LICENSE-BSD_LIKE.txt
@@ -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.
diff --git a/lucene/licenses/asm-NOTICE.txt b/lucene/licenses/asm-NOTICE.txt
new file mode 100644
index 0000000..8d1c8b6
--- /dev/null
+++ b/lucene/licenses/asm-NOTICE.txt
@@ -0,0 +1 @@
+ 
diff --git a/lucene/licenses/asm-commons-4.1.jar.sha1 b/lucene/licenses/asm-commons-4.1.jar.sha1
new file mode 100644
index 0000000..2b53475
--- /dev/null
+++ b/lucene/licenses/asm-commons-4.1.jar.sha1
@@ -0,0 +1 @@
+f8b86f4ee6e02082f63a658e00eb5506821253c6
diff --git a/lucene/licenses/asm-commons-LICENSE-BSD_LIKE.txt b/lucene/licenses/asm-commons-LICENSE-BSD_LIKE.txt
new file mode 100644
index 0000000..afb064f
--- /dev/null
+++ b/lucene/licenses/asm-commons-LICENSE-BSD_LIKE.txt
@@ -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.
diff --git a/lucene/licenses/asm-commons-NOTICE.txt b/lucene/licenses/asm-commons-NOTICE.txt
new file mode 100644
index 0000000..8d1c8b6
--- /dev/null
+++ b/lucene/licenses/asm-commons-NOTICE.txt
@@ -0,0 +1 @@
+ 
diff --git a/lucene/module-build.xml b/lucene/module-build.xml
index 6c6d97a..8be1f30 100644
--- a/lucene/module-build.xml
+++ b/lucene/module-build.xml
@@ -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"/>