private static TInfo parseTerm(FunctionQParser fp) throws SyntaxError {
    TInfo tinfo = new TInfo();

    tinfo.indexedField = tinfo.field = fp.parseArg();
    tinfo.val = fp.parseArg();
    tinfo.indexedBytes = new BytesRef();

    FieldType ft = fp.getReq().getSchema().getFieldTypeNoEx(tinfo.field);
    if (ft == null) ft = new StrField();

    if (ft instanceof TextField) {
      // need to do analysis on the term
      String indexedVal = tinfo.val;
      Query q =
          ft.getFieldQuery(fp, fp.getReq().getSchema().getFieldOrNull(tinfo.field), tinfo.val);
      if (q instanceof TermQuery) {
        Term term = ((TermQuery) q).getTerm();
        tinfo.indexedField = term.field();
        indexedVal = term.text();
      }
      UnicodeUtil.UTF16toUTF8(indexedVal, 0, indexedVal.length(), tinfo.indexedBytes);
    } else {
      ft.readableToIndexed(tinfo.val, tinfo.indexedBytes);
    }

    return tinfo;
  }
    public DelegatingCollector getFilterCollector(IndexSearcher indexSearcher) {
      try {

        SolrIndexSearcher searcher = (SolrIndexSearcher) indexSearcher;

        SortedDocValues docValues = null;
        FunctionQuery funcQuery = null;
        docValues = DocValues.getSorted(searcher.getLeafReader(), this.field);

        FieldType fieldType = null;

        if (this.max != null) {
          if (this.max.indexOf("(") == -1) {
            fieldType = searcher.getSchema().getField(this.max).getType();
          } else {
            LocalSolrQueryRequest request = null;
            try {
              SolrParams params = new ModifiableSolrParams();
              request = new LocalSolrQueryRequest(searcher.getCore(), params);
              FunctionQParser functionQParser = new FunctionQParser(this.max, null, null, request);
              funcQuery = (FunctionQuery) functionQParser.parse();
            } catch (Exception e) {
              throw new IOException(e);
            } finally {
              request.close();
            }
          }
        }

        if (this.min != null) {
          if (this.min.indexOf("(") == -1) {
            fieldType = searcher.getSchema().getField(this.min).getType();
          } else {
            LocalSolrQueryRequest request = null;
            try {
              SolrParams params = new ModifiableSolrParams();
              request = new LocalSolrQueryRequest(searcher.getCore(), params);
              FunctionQParser functionQParser = new FunctionQParser(this.min, null, null, request);
              funcQuery = (FunctionQuery) functionQParser.parse();
            } catch (Exception e) {
              throw new IOException(e);
            } finally {
              request.close();
            }
          }
        }

        int maxDoc = searcher.maxDoc();
        int leafCount = searcher.getTopReaderContext().leaves().size();

        // Deal with boosted docs.
        // We have to deal with it here rather then the constructor because
        // because the QueryElevationComponent runs after the Queries are constructed.

        IntIntOpenHashMap boostDocs = null;
        Map context = null;
        SolrRequestInfo info = SolrRequestInfo.getRequestInfo();
        if (info != null) {
          context = info.getReq().getContext();
        }

        if (this.boosted == null && context != null) {
          this.boosted =
              (Map<BytesRef, Integer>) context.get(QueryElevationComponent.BOOSTED_PRIORITY);
        }

        boostDocs = getBoostDocs(searcher, this.boosted, context);

        if (this.min != null || this.max != null) {

          return new CollapsingFieldValueCollector(
              maxDoc,
              leafCount,
              docValues,
              this.nullPolicy,
              max != null ? this.max : this.min,
              max != null,
              this.needsScores,
              fieldType,
              boostDocs,
              funcQuery,
              searcher);
        } else {
          return new CollapsingScoreCollector(
              maxDoc, leafCount, docValues, this.nullPolicy, boostDocs);
        }
      } catch (Exception e) {
        throw new RuntimeException(e);
      }
    }
  @Override
  public ValueSource parse(FunctionQParser fp) throws SyntaxError {
    String first = fp.parseArg();
    String second = fp.parseArg();
    if (first == null) first = "NOW";

    Date d1 = getDate(fp, first);
    ValueSource v1 = d1 == null ? getValueSource(fp, first) : null;

    Date d2 = getDate(fp, second);
    ValueSource v2 = d2 == null ? getValueSource(fp, second) : null;

    // d     constant
    // v     field
    // dd    constant
    // dv    subtract field from constant
    // vd    subtract constant from field
    // vv    subtract fields

    final long ms1 = (d1 == null) ? 0 : d1.getTime();
    final long ms2 = (d2 == null) ? 0 : d2.getTime();

    // "d,dd" handle both constant cases

    if (d1 != null && v2 == null) {
      return new LongConstValueSource(ms1 - ms2);
    }

    // "v" just the date field
    if (v1 != null && v2 == null && d2 == null) {
      return v1;
    }

    // "dv"
    if (d1 != null && v2 != null)
      return new DualFloatFunction(new LongConstValueSource(ms1), v2) {
        @Override
        protected String name() {
          return "ms";
        }

        @Override
        protected float func(int doc, FuncValues aVals, FuncValues bVals) {
          return ms1 - bVals.longVal(doc);
        }
      };

    // "vd"
    if (v1 != null && d2 != null)
      return new DualFloatFunction(v1, new LongConstValueSource(ms2)) {
        @Override
        protected String name() {
          return "ms";
        }

        @Override
        protected float func(int doc, FuncValues aVals, FuncValues bVals) {
          return aVals.longVal(doc) - ms2;
        }
      };

    // "vv"
    if (v1 != null && v2 != null)
      return new DualFloatFunction(v1, v2) {
        @Override
        protected String name() {
          return "ms";
        }

        @Override
        protected float func(int doc, FuncValues aVals, FuncValues bVals) {
          return aVals.longVal(doc) - bVals.longVal(doc);
        }
      };

    return null; // shouldn't happen
  }
 @Override
 public ValueSource parse(FunctionQParser fp) throws SyntaxError {
   return new Function(fp.parseValueSource(), fp.parseValueSource());
 }
  /**
   * The form of the sort specification string currently parsed is:
   *
   * <pre>
   * SortSpec ::= SingleSort [, SingleSort]*
   * SingleSort ::= &lt;fieldname|function&gt; SortDirection
   * SortDirection ::= top | desc | bottom | asc
   * </pre>
   *
   * Examples:
   *
   * <pre>
   *   score desc               #normal sort by score (will return null)
   *   weight bottom            #sort by weight ascending
   *   weight desc              #sort by weight descending
   *   height desc,weight desc  #sort by height descending, and use weight descending to break any ties
   *   height desc,weight asc   #sort by height descending, using weight ascending as a tiebreaker
   * </pre>
   *
   * @return a SortSpec object populated with the appropriate Sort (which may be null if default
   *     score sort is used) and SchemaFields (where applicable) using hardcoded default count &amp;
   *     offset values.
   */
  public static SortSpec parseSortSpec(String sortSpec, SolrQueryRequest req) {
    if (sortSpec == null || sortSpec.length() == 0) return newEmptySortSpec();

    List<SortField> sorts = new ArrayList<SortField>(4);
    List<SchemaField> fields = new ArrayList<SchemaField>(4);

    try {

      StrParser sp = new StrParser(sortSpec);
      while (sp.pos < sp.end) {
        sp.eatws();

        final int start = sp.pos;

        // short circuit test for a really simple field name
        String field = sp.getId(null);
        Exception qParserException = null;

        if (field == null || !Character.isWhitespace(sp.peekChar())) {
          // let's try it as a function instead
          field = null;
          String funcStr = sp.val.substring(start);

          QParser parser = QParser.getParser(funcStr, FunctionQParserPlugin.NAME, req);
          Query q = null;
          try {
            if (parser instanceof FunctionQParser) {
              FunctionQParser fparser = (FunctionQParser) parser;
              fparser.setParseMultipleSources(false);
              fparser.setParseToEnd(false);

              q = fparser.getQuery();

              if (fparser.localParams != null) {
                if (fparser.valFollowedParams) {
                  // need to find the end of the function query via the string parser
                  int leftOver = fparser.sp.end - fparser.sp.pos;
                  sp.pos = sp.end - leftOver; // reset our parser to the same amount of leftover
                } else {
                  // the value was via the "v" param in localParams, so we need to find
                  // the end of the local params themselves to pick up where we left off
                  sp.pos = start + fparser.localParamsEnd;
                }
              } else {
                // need to find the end of the function query via the string parser
                int leftOver = fparser.sp.end - fparser.sp.pos;
                sp.pos = sp.end - leftOver; // reset our parser to the same amount of leftover
              }
            } else {
              // A QParser that's not for function queries.
              // It must have been specified via local params.
              q = parser.getQuery();

              assert parser.getLocalParams() != null;
              sp.pos = start + parser.localParamsEnd;
            }

            Boolean top = sp.getSortDirection();
            if (null != top) {
              // we have a Query and a valid direction
              if (q instanceof FunctionQuery) {
                sorts.add(((FunctionQuery) q).getValueSource().getSortField(top));
              } else {
                sorts.add((new QueryValueSource(q, 0.0f)).getSortField(top));
              }
              fields.add(null);
              continue;
            }
          } catch (Exception e) {
            // hang onto this in case the string isn't a full field name either
            qParserException = e;
          }
        }

        // if we made it here, we either have a "simple" field name,
        // or there was a problem parsing the string as a complex func/quer

        if (field == null) {
          // try again, simple rules for a field name with no whitespace
          sp.pos = start;
          field = sp.getSimpleString();
        }
        Boolean top = sp.getSortDirection();
        if (null == top) {
          throw new SolrException(
              SolrException.ErrorCode.BAD_REQUEST,
              "Can't determine a Sort Order (asc or desc) in sort spec " + sp);
        }

        if (SCORE.equals(field)) {
          if (top) {
            sorts.add(SortField.FIELD_SCORE);
          } else {
            sorts.add(new SortField(null, SortField.Type.SCORE, true));
          }
          fields.add(null);
        } else if (DOCID.equals(field)) {
          sorts.add(new SortField(null, SortField.Type.DOC, top));
          fields.add(null);
        } else {
          // try to find the field
          SchemaField sf = req.getSchema().getFieldOrNull(field);
          if (null == sf) {
            if (null != qParserException) {
              throw new SolrException(
                  SolrException.ErrorCode.BAD_REQUEST,
                  "sort param could not be parsed as a query, and is not a "
                      + "field that exists in the index: "
                      + field,
                  qParserException);
            }
            throw new SolrException(
                SolrException.ErrorCode.BAD_REQUEST, "sort param field can't be found: " + field);
          }
          sorts.add(sf.getSortField(top));
          fields.add(sf);
        }
      }

    } catch (SyntaxError e) {
      throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "error in sort: " + sortSpec, e);
    }

    // normalize a sort on score desc to null
    if (sorts.size() == 1 && sorts.get(0) == SortField.FIELD_SCORE) {
      return newEmptySortSpec();
    }

    Sort s = new Sort(sorts.toArray(new SortField[sorts.size()]));
    return new SortSpec(s, fields);
  }