//===-- DILLexer.h ----------------------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#ifndef LLDB_VALUEOBJECT_DILLEXER_H
#define LLDB_VALUEOBJECT_DILLEXER_H

#include "llvm/ADT/StringRef.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/FormatVariadic.h"
#include <cstdint>
#include <memory>
#include <string>
#include <vector>

namespace lldb_private::dil {

/// Class defining the tokens generated by the DIL lexer and used by the
/// DIL parser.
class Token {
public:
  enum Kind {
    amp,
    arrow,
    coloncolon,
    eof,
    identifier,
    l_paren,
    l_square,
    minus,
    numeric_constant,
    period,
    r_paren,
    r_square,
    star,
  };

  Token(Kind kind, std::string spelling, uint32_t start)
      : m_kind(kind), m_spelling(std::move(spelling)), m_start_pos(start) {}

  Kind GetKind() const { return m_kind; }

  std::string GetSpelling() const { return m_spelling; }

  bool Is(Kind kind) const { return m_kind == kind; }

  bool IsNot(Kind kind) const { return m_kind != kind; }

  bool IsOneOf(llvm::ArrayRef<Kind> kinds) const {
    return llvm::is_contained(kinds, m_kind);
  }

  uint32_t GetLocation() const { return m_start_pos; }

  static llvm::StringRef GetTokenName(Kind kind);

private:
  Kind m_kind;
  std::string m_spelling;
  uint32_t m_start_pos; // within entire expression string
};

/// Class for doing the simple lexing required by DIL.
class DILLexer {
public:
  /// Lexes all the tokens in expr and calls the private constructor
  /// with the lexed tokens.
  static llvm::Expected<DILLexer> Create(llvm::StringRef expr);

  /// Return the current token to be handled by the DIL parser.
  const Token &GetCurrentToken() { return m_lexed_tokens[m_tokens_idx]; }

  /// Advance the current token position by N.
  void Advance(uint32_t N = 1) {
    if (m_tokens_idx + N >= m_lexed_tokens.size())
      // N is too large; advance to the end of the lexed tokens.
      m_tokens_idx = m_lexed_tokens.size() - 1;
    else
      m_tokens_idx += N;
  }

  /// Return the lexed token N positions ahead of the 'current' token
  /// being handled by the DIL parser.
  const Token &LookAhead(uint32_t N) {
    if (m_tokens_idx + N < m_lexed_tokens.size())
      return m_lexed_tokens[m_tokens_idx + N];

    // Last token should be an 'eof' token.
    return m_lexed_tokens.back();
  }

  /// Return the index for the 'current' token being handled by the DIL parser.
  uint32_t GetCurrentTokenIdx() { return m_tokens_idx; }

  /// Set the index for the 'current' token (to be handled by the parser)
  /// to a particular position. Used for either committing 'look ahead' parsing
  /// or rolling back tentative parsing.
  void ResetTokenIdx(uint32_t new_value) {
    assert(new_value < m_lexed_tokens.size());
    m_tokens_idx = new_value;
  }

  uint32_t NumLexedTokens() { return m_lexed_tokens.size(); }

private:
  DILLexer(llvm::StringRef dil_expr, std::vector<Token> lexed_tokens)
      : m_expr(dil_expr), m_lexed_tokens(std::move(lexed_tokens)),
        m_tokens_idx(0) {}

  static llvm::Expected<Token> Lex(llvm::StringRef expr,
                                   llvm::StringRef &remainder);

  // The input string we are lexing & parsing.
  llvm::StringRef m_expr;

  // Holds all of the tokens lexed so far.
  std::vector<Token> m_lexed_tokens;

  // Index into m_lexed_tokens; indicates which token the DIL parser is
  // currently trying to parse/handle.
  uint32_t m_tokens_idx;
};

} // namespace lldb_private::dil

namespace llvm {

template <> struct format_provider<lldb_private::dil::Token::Kind> {
  static void format(const lldb_private::dil::Token::Kind &k, raw_ostream &OS,
                     llvm::StringRef Options) {
    OS << "'" << lldb_private::dil::Token::GetTokenName(k) << "'";
  }
};

template <> struct format_provider<lldb_private::dil::Token> {
  static void format(const lldb_private::dil::Token &t, raw_ostream &OS,
                     llvm::StringRef Options) {
    lldb_private::dil::Token::Kind kind = t.GetKind();
    OS << "<'" << t.GetSpelling() << "' ("
       << lldb_private::dil::Token::GetTokenName(kind) << ")>";
  }
};

} // namespace llvm

#endif // LLDB_VALUEOBJECT_DILLEXER_H
