public Tuple<List<SyntaxElement>> partitionSyntaxElements(int partitionAtCharIndex) {
   int partAt = getElementIndex(partitionAtCharIndex);
   return CollectionUtil.partition(partAt, syntaxElements);
 }
public class SyntaxElementListener extends BaseFtListener implements OnError {
  private final boolean debug = Const.debugSyntaxElementListener;

  private static String[] sql_keywords = {
    "ALTER", "AND", "OR", "AS", "BY", "COLUMN", "CREATE", "DELETE", "DROP", "FROM", "GROUP",
    "HAVING", "INSERT", "INTO", "JOIN", "LEFT", "LIKE", "LIMIT", "OFFSET", "ON", "ORDER", "OUTER",
    "RENAME", "SELECT", "SET", "TABLE", "TO", "UPDATE", "VALUES", "VIEW", "WHERE"
  };

  private static String[] ft_keywords = {
    "DESC",
    "AVERAGE",
    "COUNT",
    "CIRCLE",
    "ASC",
    "SUM",
    "WITH",
    "ST_DISTANCE",
    "ST_INTERSECTS",
    "IN",
    "BETWEEN",
    "STARTS",
    "CASE",
    "CONTAINS",
    "RECTANGLE",
    "LATLNG",
    "DOES",
    "CONTAIN",
    "ENDS",
    "IGNORING",
    "NOT",
    "EQUAL",
    "OF",
    "MATCHES",
    "MAXIMUM",
    "MINIMUM",
  };

  private static List<String> sqlkeywords =
      CollectionUtil.sort(CollectionUtil.toList(sql_keywords));
  private static List<String> ftkeywords = CollectionUtil.sort(CollectionUtil.toList(ft_keywords));

  private SyntaxElementType currentElementType = SyntaxElementType.unknown;

  public final List<SyntaxElement> syntaxElements = new LinkedList<SyntaxElement>();

  private final FusionTablesSqlParser parser;
  public final BufferedTokenStream tokens;

  public SyntaxElementListener(FusionTablesSqlParser parser, BufferedTokenStream tokens) {
    this.parser = parser;
    this.tokens = tokens;
  }

  /**
   * @param charIndex any integer
   * @return the index of the first token where the start of the token (token.from) >=
   *     tokenIndexBegin -1 if no token of that kind exists
   */
  public int getElementIndex(int charIndex) {

    int startIdx = -1;
    for (int i = 0; i < syntaxElements.size(); i++)
      if (syntaxElements.get(i).from >= charIndex) {
        startIdx = i;
        break;
      }
    return startIdx;
  }

  public Tuple<List<SyntaxElement>> partitionSyntaxElements(int partitionAtCharIndex) {
    int partAt = getElementIndex(partitionAtCharIndex);
    return CollectionUtil.partition(partAt, syntaxElements);
  }

  private static boolean isSqlKeyword(String s) {
    return Collections.binarySearch(sqlkeywords, s.toUpperCase()) >= 0;
  }

  private static boolean isFtKeyword(String s) {
    return Collections.binarySearch(ftkeywords, s.toUpperCase()) >= 0;
  }

  private void addElement(Token token, SyntaxElementType type) {
    syntaxElements.add(
        SyntaxElement.create(
            token.getText(),
            token.getStartIndex(),
            token.getStopIndex(),
            token.getTokenIndex(),
            type));
    if (debug) {
      SyntaxElement e = syntaxElements.get(syntaxElements.size() - 1);
      System.out.println(String.format("%d-%d %s %s", e.from, e.to, e.type.name(), e.value));
    }
  }

  @Override
  public void notifyOnError(Token offendingToken, Token missingToken, IntervalSet tokensExpected) {}

  @Override
  public void enterIdentifier(FusionTablesSqlParser.IdentifierContext ctx) {
    setElementType(SyntaxElementType.identifier);
  }

  @Override
  public void exitIdentifier(FusionTablesSqlParser.IdentifierContext ctx) {
    unSetElementType(SyntaxElementType.identifier);
  }

  @Override
  public void enterTable_name(FusionTablesSqlParser.Table_nameContext ctx) {
    setElementType(SyntaxElementType.tableName);
  }

  @Override
  public void exitTable_name(FusionTablesSqlParser.Table_nameContext ctx) {
    unSetElementType(SyntaxElementType.tableName);
  }

  @Override
  public void enterColumn_name(FusionTablesSqlParser.Column_nameContext ctx) {
    setElementType(SyntaxElementType.columnName);
  }

  @Override
  public void exitColumn_name(FusionTablesSqlParser.Column_nameContext ctx) {
    unSetElementType(SyntaxElementType.columnName);
  }

  @Override
  public void enterView_name(FusionTablesSqlParser.View_nameContext ctx) {
    setElementType(SyntaxElementType.viewName);
  }

  @Override
  public void exitView_name(FusionTablesSqlParser.View_nameContext ctx) {
    unSetElementType(SyntaxElementType.viewName);
  }

  @Override
  public void enterTable_alias(FusionTablesSqlParser.Table_aliasContext ctx) {
    setElementType(SyntaxElementType.alias);
  }

  @Override
  public void exitTable_alias(FusionTablesSqlParser.Table_aliasContext ctx) {
    unSetElementType(SyntaxElementType.alias);
  }

  @Override
  public void enterOperator(FusionTablesSqlParser.OperatorContext ctx) {
    setElementType(SyntaxElementType.operator);
  }

  @Override
  public void exitOperator(FusionTablesSqlParser.OperatorContext ctx) {
    unSetElementType(SyntaxElementType.operator);
  }

  @Override
  public void enterNumeric_literal(FusionTablesSqlParser.Numeric_literalContext ctx) {
    setElementType(SyntaxElementType.numericLiteral);
  }

  @Override
  public void exitNumeric_literal(FusionTablesSqlParser.Numeric_literalContext ctx) {
    unSetElementType(SyntaxElementType.numericLiteral);
  }

  @Override
  public void enterString_literal(FusionTablesSqlParser.String_literalContext ctx) {
    setElementType(SyntaxElementType.stringLiteral);
  }

  @Override
  public void exitString_literal(FusionTablesSqlParser.String_literalContext ctx) {
    unSetElementType(SyntaxElementType.stringLiteral);
  }

  @Override
  public void visitTerminal(TerminalNode node) {
    if (currentElementType != SyntaxElementType.unknown)
      addElement(node.getSymbol(), currentElementType);
    else if (isSqlKeyword(node.getText()))
      addElement(node.getSymbol(), SyntaxElementType.sql_keyword);
    else if (isFtKeyword(node.getText()))
      addElement(node.getSymbol(), SyntaxElementType.ft_keyword);
  }

  @Override
  public void visitErrorNode(ErrorNode node) {
    if (!isGenericError(node.getText())) addElement(node.getSymbol(), SyntaxElementType.error);
  }

  private void setElementType(SyntaxElementType to) {
    if (currentElementType == SyntaxElementType.unknown) currentElementType = to;
  }

  private void unSetElementType(SyntaxElementType from) {
    if (currentElementType == from) currentElementType = SyntaxElementType.unknown;
  }
}