/*                                                       -*- C++ -*-
  Copyright (C) 2020-2022, 2025-2026 Free Software Foundation, Inc.

  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/

/* Simplified C++ Type and Expression Grammar.
   Written by Paul Hilfinger for Bison's test suite.  */

%require "3.8"
%glr-parser
%skeleton "glr2.cc"
%define parse.assert
%define api.token.constructor
%header
%locations
%debug

// Custom error messages.
%define parse.error custom

%code requires
{
  #include "ast.hh"
}

%define api.value.type variant

%code
{
  #include <cassert>
  #include <cctype>
  #include <fstream>
  #include <cstring>

  // Merge two semantic values.
  static Node
  stmt_merge (const Node& x0, const Node& x1);

  // Fetch a token.
  static yy::parser::symbol_type
  yylex ();
}

%expect-rr 1

%type <Node> TYPENAME ID stmt expr decl declarator
%printer { yyo << $$; } <Node>

%token
  TYPENAME  "typename"
  ID        "identifier"
  SEMICOLON ";"
  EQUAL     "="
  PLUS      "+"
  LPAREN    "("
  RPAREN    ")"

%right "="
%left "+"

%%

prog : %empty
     | prog stmt   { std::cout << @2 << ": " << $2 << '\n'; }
     ;

stmt : expr ";"  %merge <stmt_merge>   { $$ = $1; }
     | decl      %merge <stmt_merge>
     | error ";"                       { $$ = Nterm ("<error>"); }
     ;

expr : ID
     | TYPENAME "(" expr ")"  { $$ = Nterm ("<cast>", $3, $1); }
     | expr "+" expr          { $$ = Nterm ("+", $1, $3); }
     | expr "=" expr          { $$ = Nterm ("=", $1, $3); }
     ;

decl : TYPENAME declarator ";"
                        { $$ = Nterm ("<declare>", $1, $2); }
     | TYPENAME declarator "=" expr ";"
                        { $$ = Nterm ("<init-declare>", $1, $2, $4); }
     ;

declarator
     : ID
     | "(" declarator ")" { $$ = $2; }
     ;

%%
std::istream* input = nullptr;
yy::parser::location_type loc;


/*---------.
| Parser.  |
`---------*/

// Generate a custom error message.
void
yy::parser::report_syntax_error (const context& ctx) const
{
  std::cerr << ctx.location () << ": syntax error";
  if (!ctx.lookahead ().empty ())
    std::cerr << " on token " << ctx.lookahead ().name ();
  {
    enum { TOKENMAX = 10 };
    symbol_kind_type expected[TOKENMAX];
    int n = ctx.expected_tokens (expected, TOKENMAX);
    if (0 < n)
      {
        for (int i = 0; i < n; ++i)
          std::cerr << (i == 0 ? " (expected " : " or ")
                    << symbol_name (expected[i]);
        std::cerr << ')';
      }
  }
  std::cerr << '\n';
}

// Report the error to the user.
void
yy::parser::error (const location_type& l, const std::string& m)
{
  std::cerr << l << ": " << m << '\n';
}

// Fetch the next token.
static yy::parser::symbol_type
yylex ()
{
  while (true)
    {
      loc.step ();
      loc += 1;
      assert (!input->eof ());
      switch (int c = input->get ())
        {
        case EOF:
          return yy::parser::make_YYEOF (loc);
        case '\t':
          loc.end.column = (loc.end.column + 7) & ~7;
          loc.step ();
          break;
        case ' ': case '\f':
          loc.step ();
          break;
        case '\n':
          loc.lines (1);
          loc.end.column = 0;
          loc.step ();
          break;
        case '+':
          return yy::parser::make_PLUS (loc);
        case '=':
          return yy::parser::make_EQUAL (loc);
        case '(':
          return yy::parser::make_LPAREN (loc);
        case ')':
          return yy::parser::make_RPAREN (loc);
        case ';':
          return yy::parser::make_SEMICOLON (loc);
        default:
          if (isalpha (c))
            {
              std::string form;
              do
                {
                  form += static_cast<char> (c);
                  loc += 1;
                  c = input->get ();
                }
              while (isalnum (c) || c == '_');
              input->unget ();
              loc -= 1;
              if (isupper (static_cast <unsigned char> (form[0])))
                return yy::parser::make_TYPENAME (Term (form), loc);
              else
                return yy::parser::make_ID (Term (form), loc);
            }
          else
            {
              auto msg = "invalid character: " + std::string(1, static_cast<char> (c));
              throw yy::parser::syntax_error (loc, msg);
            }
        }
    }
}

// Merge two semantic values as an AST including both alternatives.
static Node
stmt_merge (const Node& x0, const Node& x1)
{
  return Nterm ("<OR>", x0, x1);
}


/*-------.
| Main.  |
`-------*/

// Parse `file` using parser `parse`.
int
process (yy::parser& parse, const std::string& file)
{
  bool is_stdin = file == "-" || file.empty ();
  if (is_stdin)
    input = &std::cin;
  else
    input = new std::ifstream (file.c_str ());
  loc.initialize (nullptr, 1, 0);
  int status = parse ();
  if (!is_stdin)
    delete input;
  return status;
}

int
main (int argc, char **argv)
{
  yy::parser parse;

  if (getenv ("YYDEBUG"))
    parse.set_debug_level (1);

  bool ran = false;
  for (int i = 1; i < argc; ++i)
    // Enable parse traces on option -p.
    if (strcmp (argv[i], "-p") == 0)
      parse.set_debug_level (1);
    else
      {
        int status = process (parse, argv[i]);
        ran = true;
        if (!status)
          return status;
      }

  if (!ran)
    {
      int status = process (parse, "");
      if (!status)
        return status;
    }
  return 0;
}
