| options { |
| STATIC = false; |
| JAVA_UNICODE_ESCAPE = true; |
| USER_CHAR_STREAM = true; |
| IGNORE_CASE = false; |
| JDK_VERSION = "1.8"; |
| |
| // FORCE_LA_CHECK = true; |
| // DEBUG_LOOKAHEAD = true; |
| // DEBUG_PARSER = true; |
| } |
| |
| PARSER_BEGIN(StandardSyntaxParser) |
| package org.apache.lucene.queryparser.flexible.standard.parser; |
| |
| /* |
| * 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.StringReader; |
| import java.io.Reader; |
| import java.util.Collections; |
| import java.util.ArrayList; |
| |
| import org.apache.lucene.queryparser.flexible.core.nodes.AndQueryNode; |
| import org.apache.lucene.queryparser.flexible.core.nodes.BooleanQueryNode; |
| import org.apache.lucene.queryparser.flexible.core.nodes.BoostQueryNode; |
| import org.apache.lucene.queryparser.flexible.core.nodes.FieldQueryNode; |
| import org.apache.lucene.queryparser.flexible.core.nodes.FuzzyQueryNode; |
| import org.apache.lucene.queryparser.flexible.core.nodes.GroupQueryNode; |
| import org.apache.lucene.queryparser.flexible.core.nodes.ModifierQueryNode; |
| import org.apache.lucene.queryparser.flexible.core.nodes.OrQueryNode; |
| import org.apache.lucene.queryparser.flexible.core.nodes.QueryNode; |
| import org.apache.lucene.queryparser.flexible.core.nodes.QuotedFieldQueryNode; |
| import org.apache.lucene.queryparser.flexible.core.nodes.SlopQueryNode; |
| import org.apache.lucene.queryparser.flexible.messages.Message; |
| import org.apache.lucene.queryparser.flexible.messages.MessageImpl; |
| import org.apache.lucene.queryparser.flexible.core.QueryNodeParseException; |
| import org.apache.lucene.queryparser.flexible.core.messages.QueryParserMessages; |
| import org.apache.lucene.queryparser.flexible.core.parser.SyntaxParser; |
| import org.apache.lucene.queryparser.flexible.standard.nodes.intervalfn.After; |
| import org.apache.lucene.queryparser.flexible.standard.nodes.intervalfn.AnalyzedText; |
| import org.apache.lucene.queryparser.flexible.standard.nodes.intervalfn.AtLeast; |
| import org.apache.lucene.queryparser.flexible.standard.nodes.intervalfn.Before; |
| import org.apache.lucene.queryparser.flexible.standard.nodes.intervalfn.ContainedBy; |
| import org.apache.lucene.queryparser.flexible.standard.nodes.intervalfn.Containing; |
| import org.apache.lucene.queryparser.flexible.standard.nodes.intervalfn.Extend; |
| import org.apache.lucene.queryparser.flexible.standard.nodes.intervalfn.FuzzyTerm; |
| import org.apache.lucene.queryparser.flexible.standard.nodes.intervalfn.IntervalFunction; |
| import org.apache.lucene.queryparser.flexible.standard.nodes.intervalfn.MaxGaps; |
| import org.apache.lucene.queryparser.flexible.standard.nodes.intervalfn.MaxWidth; |
| import org.apache.lucene.queryparser.flexible.standard.nodes.intervalfn.NonOverlapping; |
| import org.apache.lucene.queryparser.flexible.standard.nodes.intervalfn.NotContainedBy; |
| import org.apache.lucene.queryparser.flexible.standard.nodes.intervalfn.NotContaining; |
| import org.apache.lucene.queryparser.flexible.standard.nodes.intervalfn.NotWithin; |
| import org.apache.lucene.queryparser.flexible.standard.nodes.intervalfn.Or; |
| import org.apache.lucene.queryparser.flexible.standard.nodes.intervalfn.Ordered; |
| import org.apache.lucene.queryparser.flexible.standard.nodes.intervalfn.Overlapping; |
| import org.apache.lucene.queryparser.flexible.standard.nodes.intervalfn.Phrase; |
| import org.apache.lucene.queryparser.flexible.standard.nodes.intervalfn.Unordered; |
| import org.apache.lucene.queryparser.flexible.standard.nodes.intervalfn.UnorderedNoOverlaps; |
| import org.apache.lucene.queryparser.flexible.standard.nodes.intervalfn.Wildcard; |
| import org.apache.lucene.queryparser.flexible.standard.nodes.intervalfn.Within; |
| import org.apache.lucene.queryparser.flexible.standard.nodes.IntervalQueryNode; |
| import org.apache.lucene.queryparser.flexible.standard.nodes.MinShouldMatchNode; |
| import org.apache.lucene.queryparser.flexible.standard.nodes.RegexpQueryNode; |
| import org.apache.lucene.queryparser.charstream.CharStream; |
| import org.apache.lucene.queryparser.charstream.FastCharStream; |
| import org.apache.lucene.queryparser.flexible.standard.nodes.TermRangeQueryNode; |
| |
| import static org.apache.lucene.queryparser.flexible.standard.parser.EscapeQuerySyntaxImpl.discardEscapeChar; |
| |
| /** |
| * Parser for the standard Lucene syntax |
| */ |
| public class StandardSyntaxParser implements SyntaxParser { |
| public StandardSyntaxParser() { |
| this(new FastCharStream(Reader.nullReader())); |
| } |
| |
| /** |
| * Parses a query string, returning a {@link org.apache.lucene.queryparser.flexible.core.nodes.QueryNode}. |
| * @param query the query string to be parsed. |
| * @throws ParseException if the parsing fails |
| */ |
| @Override |
| public QueryNode parse(CharSequence query, CharSequence field) throws QueryNodeParseException { |
| ReInit(new FastCharStream(new StringReader(query.toString()))); |
| try { |
| return TopLevelQuery(field); |
| } catch (ParseException tme) { |
| tme.setQuery(query); |
| throw tme; |
| } catch (Error tme) { |
| Message message = new MessageImpl(QueryParserMessages.INVALID_SYNTAX_CANNOT_PARSE, query, tme.getMessage()); |
| QueryNodeParseException e = new QueryNodeParseException(tme); |
| e.setQuery(query); |
| e.setNonLocalizedMessage(message); |
| throw e; |
| } |
| } |
| |
| public static float parseFloat(Token token) { |
| return Float.parseFloat(token.image); |
| } |
| |
| public static int parseInt(Token token) { |
| return Integer.parseInt(token.image); |
| } |
| } |
| PARSER_END(StandardSyntaxParser) |
| |
| // Token definitions. |
| |
| <*> TOKEN : { |
| <#_NUM_CHAR: ["0"-"9"] > |
| // Every character that follows a backslash is considered as an escaped character |
| | <#_ESCAPED_CHAR: "\\" ~[] > |
| | <#_TERM_START_CHAR: ( ~[ " ", "\t", "\n", "\r", "\u3000", "+", "-", "!", "(", ")", ":", "^", "@", |
| "<", ">", "=", "[", "]", "\"", "{", "}", "~", "\\", "/"] |
| | <_ESCAPED_CHAR> ) > |
| | <#_TERM_CHAR: ( <_TERM_START_CHAR> | <_ESCAPED_CHAR> | "-" | "+" ) > |
| | <#_WHITESPACE: ( " " | "\t" | "\n" | "\r" | "\u3000") > |
| | <#_QUOTED_CHAR: ( ~[ "\"", "\\" ] | <_ESCAPED_CHAR> ) > |
| } |
| |
| <DEFAULT, Range, Function> SKIP : { |
| < <_WHITESPACE> > |
| } |
| |
| <DEFAULT> TOKEN : { |
| <AND: ("AND" | "&&") > |
| | <OR: ("OR" | "||") > |
| | <NOT: ("NOT" | "!") > |
| | <FN_PREFIX: ("fn:") > : Function |
| | <PLUS: "+" > |
| | <MINUS: "-" > |
| | <RPAREN: ")" > |
| | <OP_COLON: ":" > |
| | <OP_EQUAL: "=" > |
| | <OP_LESSTHAN: "<" > |
| | <OP_LESSTHANEQ: "<=" > |
| | <OP_MORETHAN: ">" > |
| | <OP_MORETHANEQ: ">=" > |
| | <CARAT: "^" > |
| | <TILDE: "~" > |
| | <QUOTED: "\"" (<_QUOTED_CHAR>)* "\""> |
| | <NUMBER: (<_NUM_CHAR>)+ ( "." (<_NUM_CHAR>)+ )? > |
| | <TERM: <_TERM_START_CHAR> (<_TERM_CHAR>)* > |
| | <REGEXPTERM: "/" (~[ "/" ] | "\\/" )* "/" > |
| | <RANGEIN_START: "[" > : Range |
| | <RANGEEX_START: "{" > : Range |
| } |
| |
| <DEFAULT,Function> TOKEN : { |
| <LPAREN: "(" > : DEFAULT |
| } |
| |
| <Function> TOKEN : { |
| <ATLEAST: ("atleast" | "atLeast") > |
| | <AFTER: ("after") > |
| | <BEFORE: ("before") > |
| | <CONTAINED_BY: ("containedBy" | "containedby") > |
| | <CONTAINING: ("containing") > |
| | <EXTEND: ("extend") > |
| | <FN_OR: ("or") > |
| | <FUZZYTERM: ("fuzzyterm" | "fuzzyTerm") > |
| | <MAXGAPS: ("maxgaps" | "maxGaps") > |
| | <MAXWIDTH: ("maxwidth" | "maxWidth") > |
| | <NON_OVERLAPPING: ("nonOverlapping" | "nonoverlapping") > |
| | <NOT_CONTAINED_BY: ("notContainedBy" | "notcontainedby") > |
| | <NOT_CONTAINING: ("notContaining" | "notcontaining") > |
| | <NOT_WITHIN: ("notWithin" | "notwithin") > |
| | <ORDERED: ("ordered") > |
| | <OVERLAPPING: ("overlapping") > |
| | <PHRASE: ("phrase") > |
| | <UNORDERED: ("unordered") > |
| | <UNORDERED_NO_OVERLAPS: ("unorderedNoOverlaps" | "unorderednooverlaps") > |
| | <WILDCARD: ("wildcard") > |
| | <WITHIN: ("within") > |
| } |
| |
| <Range> TOKEN : { |
| <RANGE_TO: "TO"> |
| | <RANGEIN_END: "]"> : DEFAULT |
| | <RANGEEX_END: "}"> : DEFAULT |
| | <RANGE_QUOTED: "\"" (~["\""] | "\\\"")+ "\""> |
| | <RANGE_GOOP: (~[ " ", "]", "}" ])+ > |
| } |
| |
| |
| |
| // Non-terminal production rules. |
| |
| /** |
| * The top-level rule ensures that there is no garbage after the query string. |
| * |
| * <pre>{@code |
| * TopLevelQuery ::= Query <EOF> |
| * }</pre> |
| */ |
| public QueryNode TopLevelQuery(CharSequence field) : |
| { |
| QueryNode q; |
| } |
| { |
| q = Query(field) <EOF> { |
| return q; |
| } |
| } |
| |
| /** |
| * A query consists of one or more disjunction queries (solves operator precedence). |
| * <pre>{@code |
| * Query ::= DisjQuery ( DisjQuery )* |
| * DisjQuery ::= ConjQuery ( OR ConjQuery )* |
| * ConjQuery ::= ModClause ( AND ModClause )* |
| * }</pre> |
| */ |
| private QueryNode Query(CharSequence field) : { |
| ArrayList<QueryNode> clauses = new ArrayList<QueryNode>(); |
| QueryNode node; |
| } |
| { |
| ( node = DisjQuery(field) { clauses.add(node); } )+ |
| { |
| // Handle the case of a "pure" negation query which |
| // needs to be wrapped as a boolean query, otherwise |
| // the returned result drops the negation. |
| if (clauses.size() == 1) { |
| QueryNode first = clauses.get(0); |
| if (first instanceof ModifierQueryNode |
| && ((ModifierQueryNode) first).getModifier() == ModifierQueryNode.Modifier.MOD_NOT) { |
| clauses.set(0, new BooleanQueryNode(Collections.singletonList(first))); |
| } |
| } |
| |
| return clauses.size() == 1 ? clauses.get(0) : new BooleanQueryNode(clauses); |
| } |
| } |
| |
| /** |
| * A disjoint clause consists of one or more conjunction clauses. |
| * <pre>{@code |
| * DisjQuery ::= ConjQuery ( OR ConjQuery )* |
| * }</pre> |
| */ |
| private QueryNode DisjQuery(CharSequence field) : { |
| ArrayList<QueryNode> clauses = new ArrayList<QueryNode>(); |
| QueryNode node; |
| } |
| { |
| node = ConjQuery(field) { clauses.add(node); } |
| ( <OR> node = ConjQuery(field) { clauses.add(node); } )* |
| { |
| return clauses.size() == 1 ? clauses.get(0) : new OrQueryNode(clauses); |
| } |
| } |
| |
| /** |
| * A conjunction clause consists of one or more modifier-clause pairs. |
| * <pre>{@code |
| * ConjQuery ::= ModClause ( AND ModClause )* |
| * }</pre> |
| */ |
| private QueryNode ConjQuery(CharSequence field) : { |
| ArrayList<QueryNode> clauses = new ArrayList<QueryNode>(); |
| QueryNode node; |
| } |
| { |
| node = ModClause(field) { clauses.add(node); } |
| ( <AND> node = ModClause(field) { clauses.add(node); } )* |
| { |
| return clauses.size() == 1 ? clauses.get(0) : new AndQueryNode(clauses); |
| } |
| } |
| |
| /** |
| * A modifier-atomic clause pair. |
| * <pre>{@code |
| * ModClause ::= (Modifier)? Clause |
| * }</pre> |
| */ |
| private QueryNode ModClause(CharSequence field) : { |
| QueryNode q; |
| ModifierQueryNode.Modifier modifier = ModifierQueryNode.Modifier.MOD_NONE; |
| } |
| { |
| ( <PLUS> { modifier = ModifierQueryNode.Modifier.MOD_REQ; } |
| | (<MINUS> | <NOT>) { modifier = ModifierQueryNode.Modifier.MOD_NOT; } |
| )? |
| q = Clause(field) |
| { |
| if (modifier != ModifierQueryNode.Modifier.MOD_NONE) { |
| q = new ModifierQueryNode(q, modifier); |
| } |
| return q; |
| } |
| } |
| |
| /** |
| * An atomic clause consists of a field range expression, a potentially |
| * field-qualified term or a group. |
| * |
| * <pre>{@code |
| * Clause ::= FieldRangeExpr |
| * | (FieldName (':' | '='))? (Term | GroupingExpr) |
| * }</pre> |
| */ |
| private QueryNode Clause(CharSequence field) : { |
| QueryNode q; |
| } |
| { |
| ( |
| LOOKAHEAD(2) q = FieldRangeExpr(field) |
| | (LOOKAHEAD(2) field = FieldName() ( <OP_COLON> | <OP_EQUAL> ))? |
| (LOOKAHEAD(2) q = Term(field) | q = GroupingExpr(field) | q = IntervalExpr(field)) |
| ) |
| { |
| return q; |
| } |
| } |
| |
| /** |
| * A field name. This utility method strips escape characters from field names. |
| */ |
| private CharSequence FieldName() : { |
| Token name; |
| } |
| { |
| name = <TERM> { return discardEscapeChar(name.image); } |
| } |
| |
| /** |
| * An grouping expression is a Query with potential boost applied to it. |
| * |
| * <pre>{@code |
| * GroupingExpr ::= '(' Query ')' ('^' <NUMBER>)? |
| * }</pre> |
| */ |
| private QueryNode GroupingExpr(CharSequence field) : { |
| QueryNode q; |
| Token boost, minShouldMatch = null; |
| } |
| { |
| <LPAREN> q = Query(field) <RPAREN> (q = Boost(q))? ("@" minShouldMatch = <NUMBER>)? |
| { |
| if (minShouldMatch != null) { |
| q = new MinShouldMatchNode(parseInt(minShouldMatch), new GroupQueryNode(q)); |
| } else { |
| q = new GroupQueryNode(q); |
| } |
| return q; |
| } |
| } |
| |
| |
| /** |
| * An interval expression (functions) node. |
| */ |
| private IntervalQueryNode IntervalExpr(CharSequence field) : { |
| IntervalFunction source; |
| } |
| { |
| source = IntervalFun() |
| { |
| return new IntervalQueryNode(field == null ? null : field.toString(), source); |
| } |
| } |
| |
| private IntervalFunction IntervalFun() : { |
| IntervalFunction source; |
| } |
| { |
| LOOKAHEAD(2) source = IntervalAtLeast() { return source; } |
| | LOOKAHEAD(2) source = IntervalMaxWidth() { return source; } |
| | LOOKAHEAD(2) source = IntervalMaxGaps() { return source; } |
| | LOOKAHEAD(2) source = IntervalOrdered() { return source; } |
| | LOOKAHEAD(2) source = IntervalUnordered() { return source; } |
| | LOOKAHEAD(2) source = IntervalUnorderedNoOverlaps() { return source; } |
| | LOOKAHEAD(2) source = IntervalOr() { return source; } |
| | LOOKAHEAD(2) source = IntervalWildcard() { return source; } |
| | LOOKAHEAD(2) source = IntervalAfter() { return source; } |
| | LOOKAHEAD(2) source = IntervalBefore() { return source; } |
| | LOOKAHEAD(2) source = IntervalPhrase() { return source; } |
| | LOOKAHEAD(2) source = IntervalContaining() { return source; } |
| | LOOKAHEAD(2) source = IntervalNotContaining() { return source; } |
| | LOOKAHEAD(2) source = IntervalContainedBy() { return source; } |
| | LOOKAHEAD(2) source = IntervalNotContainedBy() { return source; } |
| | LOOKAHEAD(2) source = IntervalWithin() { return source; } |
| | LOOKAHEAD(2) source = IntervalNotWithin() { return source; } |
| | LOOKAHEAD(2) source = IntervalOverlapping() { return source; } |
| | LOOKAHEAD(2) source = IntervalNonOverlapping() { return source; } |
| | LOOKAHEAD(2) source = IntervalExtend() { return source; } |
| | LOOKAHEAD(2) source = IntervalFuzzyTerm() { return source; } |
| | LOOKAHEAD(2) source = IntervalText() { return source; } |
| } |
| |
| private IntervalFunction IntervalAtLeast() : { |
| IntervalFunction source; |
| ArrayList<IntervalFunction> sources = new ArrayList<IntervalFunction>(); |
| Token minShouldMatch; |
| } |
| { |
| <FN_PREFIX> <ATLEAST> |
| <LPAREN> minShouldMatch = <NUMBER> (source = IntervalFun() { sources.add(source); })+ <RPAREN> |
| { |
| return new AtLeast(parseInt(minShouldMatch), sources); |
| } |
| } |
| |
| private IntervalFunction IntervalMaxWidth() : { |
| IntervalFunction source; |
| Token maxWidth; |
| } |
| { |
| <FN_PREFIX> <MAXWIDTH> |
| <LPAREN> maxWidth = <NUMBER> source = IntervalFun() <RPAREN> |
| { |
| return new MaxWidth(parseInt(maxWidth), source); |
| } |
| } |
| |
| private IntervalFunction IntervalMaxGaps() : { |
| IntervalFunction source; |
| Token maxGaps; |
| } |
| { |
| <FN_PREFIX> <MAXGAPS> |
| <LPAREN> maxGaps = <NUMBER> source = IntervalFun() <RPAREN> |
| { |
| return new MaxGaps(parseInt(maxGaps), source); |
| } |
| } |
| |
| private IntervalFunction IntervalUnordered() : { |
| IntervalFunction source; |
| ArrayList<IntervalFunction> sources = new ArrayList<IntervalFunction>(); |
| } |
| { |
| <FN_PREFIX> <UNORDERED> |
| <LPAREN> (source = IntervalFun() { sources.add(source); })+ <RPAREN> |
| { |
| return new Unordered(sources); |
| } |
| } |
| |
| private IntervalFunction IntervalUnorderedNoOverlaps() : { |
| IntervalFunction a, b; |
| } |
| { |
| <FN_PREFIX> <UNORDERED_NO_OVERLAPS> |
| <LPAREN> a = IntervalFun() b = IntervalFun() <RPAREN> |
| { |
| return new UnorderedNoOverlaps(a, b); |
| } |
| } |
| |
| private IntervalFunction IntervalOrdered() : { |
| IntervalFunction source; |
| ArrayList<IntervalFunction> sources = new ArrayList<IntervalFunction>(); |
| } |
| { |
| <FN_PREFIX> <ORDERED> |
| <LPAREN> (source = IntervalFun() { sources.add(source); })+ <RPAREN> |
| { |
| return new Ordered(sources); |
| } |
| } |
| |
| private IntervalFunction IntervalOr() : { |
| IntervalFunction source; |
| ArrayList<IntervalFunction> sources = new ArrayList<IntervalFunction>(); |
| } |
| { |
| <FN_PREFIX> <FN_OR> |
| <LPAREN> (source = IntervalFun() { sources.add(source); })+ <RPAREN> |
| { |
| return new Or(sources); |
| } |
| } |
| |
| private IntervalFunction IntervalPhrase() : { |
| IntervalFunction source; |
| ArrayList<IntervalFunction> sources = new ArrayList<IntervalFunction>(); |
| } |
| { |
| <FN_PREFIX> <PHRASE> |
| <LPAREN> (source = IntervalFun() { sources.add(source); })+ <RPAREN> |
| { |
| return new Phrase(sources); |
| } |
| } |
| |
| private IntervalFunction IntervalBefore() : { |
| IntervalFunction source; |
| IntervalFunction reference; |
| } |
| { |
| <FN_PREFIX> <BEFORE> <LPAREN> source = IntervalFun() reference = IntervalFun() <RPAREN> |
| { |
| return new Before(source, reference); |
| } |
| } |
| |
| private IntervalFunction IntervalAfter() : { |
| IntervalFunction source; |
| IntervalFunction reference; |
| } |
| { |
| <FN_PREFIX> <AFTER> <LPAREN> source = IntervalFun() reference = IntervalFun() <RPAREN> |
| { |
| return new After(source, reference); |
| } |
| } |
| |
| private IntervalFunction IntervalContaining() : { |
| IntervalFunction big; |
| IntervalFunction small; |
| } |
| { |
| <FN_PREFIX> <CONTAINING> <LPAREN> big = IntervalFun() small = IntervalFun() <RPAREN> |
| { |
| return new Containing(big, small); |
| } |
| } |
| |
| private IntervalFunction IntervalNotContaining() : { |
| IntervalFunction minuend; |
| IntervalFunction subtrahend; |
| } |
| { |
| <FN_PREFIX> <NOT_CONTAINING> <LPAREN> minuend = IntervalFun() subtrahend = IntervalFun() <RPAREN> |
| { |
| return new NotContaining(minuend, subtrahend); |
| } |
| } |
| |
| private IntervalFunction IntervalContainedBy() : { |
| IntervalFunction big; |
| IntervalFunction small; |
| } |
| { |
| <FN_PREFIX> <CONTAINED_BY> <LPAREN> small = IntervalFun() big = IntervalFun() <RPAREN> |
| { |
| return new ContainedBy(small, big); |
| } |
| } |
| |
| private IntervalFunction IntervalNotContainedBy() : { |
| IntervalFunction big; |
| IntervalFunction small; |
| } |
| { |
| <FN_PREFIX> <NOT_CONTAINED_BY> <LPAREN> small = IntervalFun() big = IntervalFun() <RPAREN> |
| { |
| return new NotContainedBy(small, big); |
| } |
| } |
| |
| private IntervalFunction IntervalWithin() : { |
| IntervalFunction source, reference; |
| Token positions; |
| } |
| { |
| <FN_PREFIX> <WITHIN> |
| <LPAREN> |
| source = IntervalFun() |
| positions = <NUMBER> |
| reference = IntervalFun() |
| <RPAREN> |
| { |
| return new Within(source, parseInt(positions), reference); |
| } |
| } |
| |
| private IntervalFunction IntervalExtend() : { |
| IntervalFunction source; |
| Token before, after; |
| } |
| { |
| <FN_PREFIX> <EXTEND> |
| <LPAREN> |
| source = IntervalFun() |
| before = <NUMBER> |
| after = <NUMBER> |
| <RPAREN> |
| { |
| return new Extend(source, parseInt(before), parseInt(after)); |
| } |
| } |
| |
| private IntervalFunction IntervalNotWithin() : { |
| IntervalFunction minuend, subtrahend; |
| Token positions; |
| } |
| { |
| <FN_PREFIX> <NOT_WITHIN> |
| <LPAREN> |
| minuend = IntervalFun() |
| positions = <NUMBER> |
| subtrahend = IntervalFun() |
| <RPAREN> |
| { |
| return new NotWithin(minuend, parseInt(positions), subtrahend); |
| } |
| } |
| |
| private IntervalFunction IntervalOverlapping() : { |
| IntervalFunction source, reference; |
| } |
| { |
| <FN_PREFIX> <OVERLAPPING> <LPAREN> source = IntervalFun() reference = IntervalFun() <RPAREN> |
| { |
| return new Overlapping(source, reference); |
| } |
| } |
| |
| private IntervalFunction IntervalNonOverlapping() : { |
| IntervalFunction minuend, subtrahend; |
| } |
| { |
| <FN_PREFIX> <NON_OVERLAPPING> <LPAREN> minuend = IntervalFun() subtrahend = IntervalFun() <RPAREN> |
| { |
| return new NonOverlapping(minuend, subtrahend); |
| } |
| } |
| |
| private IntervalFunction IntervalWildcard() : { |
| String wildcard; |
| Token maxExpansions = null; |
| } |
| { |
| <FN_PREFIX> <WILDCARD> |
| <LPAREN> |
| ( |
| (<TERM> | <NUMBER>) { wildcard = token.image; } |
| | <QUOTED> { wildcard = token.image.substring(1, token.image.length() - 1); } |
| ) |
| (maxExpansions = <NUMBER>)? |
| <RPAREN> |
| { |
| return new Wildcard(wildcard, maxExpansions == null ? 0 : parseInt(maxExpansions)); |
| } |
| } |
| |
| private IntervalFunction IntervalFuzzyTerm() : { |
| String term; |
| Token maxEdits = null; |
| Token maxExpansions = null; |
| } |
| { |
| <FN_PREFIX> <FUZZYTERM> |
| <LPAREN> |
| ( |
| (<TERM> | <NUMBER>) { term = token.image; } |
| | <QUOTED> { term = token.image.substring(1, token.image.length() - 1); } |
| ) |
| (LOOKAHEAD(2) maxEdits = <NUMBER>)? |
| (LOOKAHEAD(2) maxExpansions = <NUMBER>)? |
| <RPAREN> |
| { |
| return new FuzzyTerm(term, |
| maxEdits == null ? null : parseInt(maxEdits), |
| maxExpansions == null ? null : parseInt(maxExpansions)); |
| } |
| } |
| |
| private IntervalFunction IntervalText() : { |
| } |
| { |
| (<QUOTED>) { return new AnalyzedText(token.image.substring(1, token.image.length() - 1)); } |
| | (<TERM> | <NUMBER>) { return new AnalyzedText(token.image); } |
| } |
| |
| /** |
| * Score boost modifier. |
| * |
| * <pre>{@code |
| * Boost ::= '^' <NUMBER> |
| * }</pre> |
| */ |
| private QueryNode Boost(QueryNode node) : { |
| Token boost; |
| } |
| { |
| <CARAT> boost = <NUMBER> |
| { |
| return node == null ? node : new BoostQueryNode(node, parseFloat(boost)); |
| } |
| } |
| |
| /** |
| * Fuzzy term modifier. |
| * |
| * <pre>{@code |
| * Fuzzy ::= '~' <NUMBER>? |
| * }</pre> |
| */ |
| private QueryNode FuzzyOp(CharSequence field, Token term, QueryNode node) : { |
| Token similarity = null; |
| } |
| { |
| <TILDE> (LOOKAHEAD(2) similarity = <NUMBER>)? |
| { |
| float fms = org.apache.lucene.search.FuzzyQuery.defaultMaxEdits; |
| if (similarity != null) { |
| fms = parseFloat(similarity); |
| if (fms < 0.0f) { |
| throw new ParseException(new MessageImpl(QueryParserMessages.INVALID_SYNTAX_FUZZY_LIMITS)); |
| } else if (fms >= 1.0f && fms != (int) fms) { |
| throw new ParseException(new MessageImpl(QueryParserMessages.INVALID_SYNTAX_FUZZY_EDITS)); |
| } |
| } |
| return new FuzzyQueryNode(field, discardEscapeChar(term.image), fms, term.beginColumn, term.endColumn); |
| } |
| } |
| |
| /** |
| * A field range expression selects all field values larger/ smaller (or equal) than a given one. |
| * <pre>{@code |
| * FieldRangeExpr ::= FieldName ('<' | '>' | '<=' | '>=') (<TERM> | <QUOTED> | <NUMBER>) |
| * }</pre> |
| */ |
| private TermRangeQueryNode FieldRangeExpr(CharSequence field) : { |
| Token operator, term; |
| FieldQueryNode qLower, qUpper; |
| boolean lowerInclusive, upperInclusive; |
| } |
| { |
| field = FieldName() |
| ( <OP_LESSTHAN> | <OP_LESSTHANEQ> | <OP_MORETHAN> | <OP_MORETHANEQ>) { operator = token; } |
| ( <TERM> | <QUOTED> | <NUMBER>) { term = token; } |
| { |
| if (term.kind == QUOTED) { |
| term.image = term.image.substring(1, term.image.length() - 1); |
| } |
| switch (operator.kind) { |
| case OP_LESSTHAN: |
| lowerInclusive = true; |
| upperInclusive = false; |
| qLower = new FieldQueryNode(field, "*", term.beginColumn, term.endColumn); |
| qUpper = new FieldQueryNode(field, discardEscapeChar(term.image), term.beginColumn, term.endColumn); |
| break; |
| case OP_LESSTHANEQ: |
| lowerInclusive = true; |
| upperInclusive = true; |
| qLower = new FieldQueryNode(field, "*", term.beginColumn, term.endColumn); |
| qUpper = new FieldQueryNode(field, discardEscapeChar(term.image), term.beginColumn, term.endColumn); |
| break; |
| case OP_MORETHAN: |
| lowerInclusive = false; |
| upperInclusive = true; |
| qLower = new FieldQueryNode(field, discardEscapeChar(term.image), term.beginColumn, term.endColumn); |
| qUpper = new FieldQueryNode(field, "*", term.beginColumn, term.endColumn); |
| break; |
| case OP_MORETHANEQ: |
| lowerInclusive = true; |
| upperInclusive = true; |
| qLower = new FieldQueryNode(field, discardEscapeChar(term.image), term.beginColumn, term.endColumn); |
| qUpper = new FieldQueryNode(field, "*", term.beginColumn, term.endColumn); |
| break; |
| default: |
| throw new Error("Unhandled case, operator=" + operator); |
| } |
| return new TermRangeQueryNode(qLower, qUpper, lowerInclusive, upperInclusive); |
| } |
| } |
| |
| /** |
| * A term expression. |
| * |
| * <pre>{@code |
| * Term ::= (<TERM> | <NUMBER>) ('~' <NUM>)? ('^' <NUM>)? |
| * | <REGEXPTERM> ('^' <NUM>)? |
| * | TermRangeExpr ('^' <NUM>)? |
| * | QuotedTerm ('^' <NUM>)? |
| * }</pre> |
| */ |
| private QueryNode Term(CharSequence field) : { |
| QueryNode q; |
| Token term, fuzzySlop=null; |
| } |
| { |
| ( |
| term = <REGEXPTERM> |
| { |
| String v = term.image.substring(1, term.image.length() - 1); |
| q = new RegexpQueryNode(field, v, 0, v.length()); |
| } |
| | (term = <TERM> | term = <NUMBER>) |
| { q = new FieldQueryNode(field, discardEscapeChar(term.image), term.beginColumn, term.endColumn); } |
| ( q = FuzzyOp(field, term, q) )? |
| | q = TermRangeExpr(field) |
| | q = QuotedTerm(field) |
| ) |
| ( q = Boost(q) )? |
| { |
| return q; |
| } |
| } |
| |
| |
| /** |
| * A quoted term (phrase). |
| * |
| * <pre>{@code |
| * QuotedTerm ::= <QUOTED> ('~' <NUM>)? |
| * }</pre> |
| */ |
| private QueryNode QuotedTerm(CharSequence field) : { |
| QueryNode q; |
| Token term, slop; |
| } |
| { |
| term = <QUOTED> |
| { |
| String image = term.image.substring(1, term.image.length() - 1); |
| q = new QuotedFieldQueryNode(field, discardEscapeChar(image), term.beginColumn + 1, term.endColumn - 1); |
| } |
| ( <TILDE> slop = <NUMBER> { q = new SlopQueryNode(q, parseInt(slop)); } )? |
| { |
| return q; |
| } |
| } |
| |
| /** |
| * A value range expression. |
| * |
| * <pre>{@code |
| * TermRangeExpr ::= ('[' | '{') <RANGE_START> 'TO' <RANGE_END> (']' | '}') |
| * }</pre> |
| */ |
| private TermRangeQueryNode TermRangeExpr(CharSequence field) : { |
| Token left, right; |
| boolean leftInclusive = false; |
| boolean rightInclusive = false; |
| } |
| { |
| // RANGE_TO can be consumed as range start/end because this needs to be accepted as a valid range: |
| // [TO TO TO] |
| ( |
| (<RANGEIN_START> { leftInclusive = true; } | <RANGEEX_START>) |
| (<RANGE_GOOP> | <RANGE_QUOTED> | <RANGE_TO>) { left = token; } |
| <RANGE_TO> |
| (<RANGE_GOOP> | <RANGE_QUOTED> | <RANGE_TO>) { right = token; } |
| (<RANGEIN_END> { rightInclusive = true; } | <RANGEEX_END>) |
| ) |
| |
| { |
| if (left.kind == RANGE_QUOTED) { |
| left.image = left.image.substring(1, left.image.length() - 1); |
| } |
| if (right.kind == RANGE_QUOTED) { |
| right.image = right.image.substring(1, right.image.length() - 1); |
| } |
| |
| FieldQueryNode qLower = new FieldQueryNode(field, |
| discardEscapeChar(left.image), left.beginColumn, left.endColumn); |
| FieldQueryNode qUpper = new FieldQueryNode(field, |
| discardEscapeChar(right.image), right.beginColumn, right.endColumn); |
| |
| return new TermRangeQueryNode(qLower, qUpper, leftInclusive, rightInclusive); |
| } |
| } |