/* BEGIN LEXER */
%{

#define t8_LEX_CC_

#include <cctype>
#include <memory>
#include <string>
#include <iostream>
#include <sstream>

#include "t8.yacc.hh"

namespace t8 {

class AstNode;

// ParseDriver initiates a parse and contains the state data used during a
// single parsing session (that is, during the parse of one file or string).
class ParseDriver : public yyFlexLexer {
 public:
  ParseDriver(std::istream& source, std::ostream& errors);
  ~ParseDriver() override;

  // A function used by the lexer to report errors.
  void Error(const YYParser::location_type& loc, const std::string& msg);
  void Error(const std::string& msg);
  void Fatal(const YYParser::location_type& loc, const std::string& msg);
  void LexerError(const char* msg) override;
  void LexerError(const string& msg) { LexerError(msg.c_str()); }

  // Flex-generated function to get the next token from input
  virtual YYParser::symbol_type NextToken(ParseDriver* parser);

  // Unput a char and reset location.
  virtual void Unput(char c);

  void set_initial_token(YYParser::token_type token) { initial_token_ = token; }

  void consume_initial_token(YYParser::token_type* token) {
    *token = initial_token_;
    initial_token_ = YYParser::token::TOK_EOF;
  }

  int numErrors() { return num_errors_; }

 protected:
  const YYParser::location_type* location() { return &location_; }

 private:
  std::istream* source_;  // Not owned.
  std::ostream& errors_;
  int num_errors_ = 0;
  YYParser::location_type location_;
  std::unique_ptr<std::string> file_name_;
  YYParser::token_type initial_token_;
  std::stringstream current_token_;  // a string token being built up

  // Disallow copy and assign.
  ParseDriver(const ParseDriver&) = delete;
  void operator=(const ParseDriver&) = delete;
};

}  // namespace t8

using t8::YYParser;

#undef YY_DECL
#define YY_DECL \
  YYParser::symbol_type t8::ParseDriver::NextToken(t8::ParseDriver* parser)

typedef YYParser::token token;

#define yylloc location_
#define yyterminate() return YYParser::make_TOK_EOF(yylloc)
#define YY_USER_ACTION yylloc.columns(yyleng);

%}

%option 8bit
%option batch
%option c++
%option full
%option noyywrap
%option yyclass="t8::ParseDriver"

%x string_literal
%x blank
%x comment

EOL    \r\n|\n
DDIGIT [[:digit:]]
ALNUM  [[:alnum:]_]
ALPHA  [[:alpha:]_]
BLANK  [[:blank:]\t]

DDIGITS  {DDIGIT}(_?{DDIGIT})*
EXPONENT {EXP}{SIGN}?{DDIGITS}

%%

%{
  yylloc.step();
%}

false {
    return YYParser::make_TOK_BOOL(false, yylloc);
}

true {
    return YYParser::make_TOK_BOOL(true, yylloc);
}

  /* BEGIN GENERATED TOKENS */
  /* END GENERATED TOKENS */

<INITIAL><<EOF>>  {
    return YYParser::make_TOK_EOF(yylloc);
  }

'  {
    current_token_.str("");
    BEGIN(string_literal);
}

\/\/ {
    BEGIN(comment);
}

<string_literal>{

  ''  {
       current_token_ << '\'';
    }

  \'  {
      BEGIN(INITIAL);
      return YYParser::make_TOK_STRING_LITERAL(current_token_.str(), yylloc);
    }

  {EOL}  {
      BEGIN(INITIAL);
      yylloc.lines(1);
      LexerError("string not terminated: '" + current_token_.str() + "'");
    }

  [[:cntrl:]]  {
      LexerError("unexpected character: " + std::to_string(YYText()[0]));
    }

  <<EOF>>  {
      BEGIN(INITIAL);
      LexerError("string not terminated: '" + current_token_.str() + "'");
    }

  .  {
      current_token_ << YYText();
    }
  }

<comment>{
  {EOL}  {
    BEGIN(INITIAL);
    Unput('\n');
  }
}

{BLANK}+  {
  }

  /* integer literal */
{DDIGITS}  {
    return YYParser::make_TOK_INT64(strtol(YYText(), nullptr, 10), yylloc);
  }

  /* identifier */
{ALPHA}(-*{ALNUM})*  {
    return YYParser::make_TOK_IDENTIFIER(YYText(), yylloc);
  }

  /* Any other character. */
.  {
    if (isprint(YYText()[0])) {
      LexerError(string("unexpected character: '") + YYText() + "'");
    } else {
      LexerError("unexpected character: " + std::to_string(YYText()[0]));
    }
  }

%%

namespace t8 {

using std::istream;
using std::ostream;

class FatalParseError {};

AstNode* parse(istream& input, ostream& errors) {
  ParseDriver parser(input, errors);
  AstNode* result = nullptr;
  YYParser yyparser(&parser, &result);
  int ret_val = 1;
  try { ret_val = yyparser.parse(); }
  catch (FatalParseError) {}
  if (ret_val || parser.numErrors() > 0) {
    errors << parser.numErrors() << " errors.\n";
    return nullptr;
  }
  return result;
}

YYParser::symbol_type yylex(ParseDriver* parser) {
  return parser->NextToken(parser);
}

void YYParser::error(const location_type& loc, const std::string& msg) {
  parser->Error(loc, msg);
}

ParseDriver::ParseDriver(std::istream& source, std::ostream& errors)
    : yyFlexLexer(&source)
    , source_(&source)
    , errors_(errors)
    , initial_token_(YYParser::token::TOK_EOF) {}

ParseDriver::~ParseDriver() {
  // This should be done by the parent, but it uses delete, not delete[]...
  delete[] yy_state_buf;
  yy_state_buf = NULL;

  if (yy_start_stack != NULL) {
    free(yy_start_stack);
    yy_start_stack = NULL;
  }
}

void ParseDriver::Error(
    const YYParser::location_type& loc, const std::string& msg) {
  errors_ << "ERROR: " << loc << ":" << msg << std::endl;
  num_errors_++;
}

void ParseDriver::Error(const std::string& msg) {
  errors_ << "ERROR: " << msg << std::endl;
  num_errors_++;
}

void ParseDriver::Fatal(
    const YYParser::location_type& loc, const std::string& msg) {
  Error(loc, msg);
  throw FatalParseError();
}

void ParseDriver::LexerError(const char* msg) {
  Fatal(location_, msg);
}

void ParseDriver::Unput(char c) {
  assert(location_.end.column > 0);
  location_.end.column--;
  yyunput(c, yytext);
}

}  // namespace t8
/* END LEXER */
