/*
   Copyright (C) 2021-2022 Free Software Foundation, Inc.

   This file is part of GAS, the GNU Assembler.

   GAS 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, or (at your option)
   any later version.

   GAS 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; see the file COPYING3.  If not,
   see <http://www.gnu.org/licenses/>.  */
%{
#include "as.h"
#include "loongarch-lex.h"
#include "loongarch-parse.h"
static void yyerror (const char *s ATTRIBUTE_UNUSED)
{
};
int yylex (void);


static struct reloc_info *top, *end;

static expressionS const_0 =
{
  .X_op = O_constant,
  .X_add_number = 0
};

static int
is_const (struct reloc_info *info)
{
  return (info->type == BFD_RELOC_LARCH_SOP_PUSH_ABSOLUTE
	  && info->value.X_op == O_constant);
}

int
loongarch_parse_expr (const char *expr,
		      struct reloc_info *reloc_stack_top,
		      size_t max_reloc_num,
		      size_t *reloc_num,
		      offsetT *imm)
{
  int ret;
  struct yy_buffer_state *buffstate;
  top = reloc_stack_top;
  end = top + max_reloc_num;
  buffstate = yy_scan_string (expr);
  ret = yyparse ();

  if (ret == 0)
    {
      if (is_const (top - 1))
	*imm = (--top)->value.X_add_number;
      else
	*imm = 0;
      *reloc_num = top - reloc_stack_top;
    }
  yy_delete_buffer (buffstate);

  return ret;
}

static void
emit_const (offsetT imm)
{
  if (end <= top)
    as_fatal (_("expr too huge"));
  top->type = BFD_RELOC_LARCH_SOP_PUSH_ABSOLUTE;
  top->value.X_op = O_constant;
  top->value.X_add_number = imm;
  top++;
}

static const char *
my_getExpression (expressionS *ep, const char *str)
{
  char *save_in, *ret;
  if (*str == ':')
    {
      unsigned long j;
      char *str_1 = (char *) str;
      str_1++;
      j = strtol (str_1, &str_1, 10);
      get_internal_label (ep, j, *str_1 == 'f');
      return NULL;
    }
  save_in = input_line_pointer;
  input_line_pointer = (char *)str;
  expression (ep);
  ret = input_line_pointer;
  input_line_pointer = save_in;
  return ret;
}

static void
reloc (const char *op_c_str, const char *id_c_str, offsetT addend)
{
  expressionS id_sym_expr;

  if (end <= top)
    as_fatal (_("expr too huge"));

  if (id_c_str)
    {
      my_getExpression (&id_sym_expr, id_c_str);
      id_sym_expr.X_add_number += addend;
    }
  else
    {
      id_sym_expr.X_op = O_constant;
      id_sym_expr.X_add_number = addend;
    }

  if (strcmp (op_c_str, "abs") == 0)
    {
      top->value = id_sym_expr;
      top->type = BFD_RELOC_LARCH_SOP_PUSH_ABSOLUTE;
      top++;
    }
  else if (strcmp (op_c_str, "pcrel") == 0)
    {
      top->value = id_sym_expr;
      top->type = BFD_RELOC_LARCH_SOP_PUSH_PCREL;
      top++;
    }
  else if (strcmp (op_c_str, "gprel") == 0)
    {
      top->value = id_sym_expr;
      top->type = BFD_RELOC_LARCH_SOP_PUSH_GPREL;
      top++;
    }
  else if (strcmp (op_c_str, "tprel") == 0)
    {
      top->value = id_sym_expr;
      top->type = BFD_RELOC_LARCH_SOP_PUSH_TLS_TPREL;
      top++;
    }
  else if (strcmp (op_c_str, "tlsgot") == 0)
    {
      top->value = id_sym_expr;
      top->type = BFD_RELOC_LARCH_SOP_PUSH_TLS_GOT;
      top++;
    }
  else if (strcmp (op_c_str, "tlsgd") == 0)
    {
      top->value = id_sym_expr;
      top->type = BFD_RELOC_LARCH_SOP_PUSH_TLS_GD;
      top++;
    }
  else if (strcmp (op_c_str, "plt") == 0)
    {
      top->value = id_sym_expr;
      top->type = BFD_RELOC_LARCH_SOP_PUSH_PLT_PCREL;
      top++;
    }
  else
    as_fatal (_("unknown reloc hint: %s"), op_c_str);
}

static void
emit_unary (char op)
{
  struct reloc_info *s_top = top - 1;
  if (is_const (s_top))
    {
      offsetT opr = s_top->value.X_add_number;
      switch (op)
	{
	case '+':
	  break;
	case '-':
	  opr = -opr;
	  break;
	case '~':
	  opr = ~opr;
	  break;
	case '!':
	  opr = !opr;
	  break;
	default:
	  abort ();
	}
      s_top->value.X_add_number = opr;
    }
  else
    {
      if (end <= top)
	as_fatal (_("expr too huge"));
      switch (op)
	{
	case '!':
	  top->type = BFD_RELOC_LARCH_SOP_NOT;
	  break;
	default:
	  abort ();
	}
      top->value = const_0;
      top++;
    }
}

static void
emit_bin (int op)
{
  struct reloc_info *last_1st = top - 1, *last_2nd = top - 2;
  if (is_const (last_1st) && is_const (last_2nd))
    {
      offsetT opr1 = last_2nd->value.X_add_number;
      offsetT opr2 = last_1st->value.X_add_number;
      switch (op)
	{
	case '*':
	  opr1 = opr1 * opr2;
	  break;
	case '/':
	  opr1 = opr1 / opr2;
	  break;
	case '%':
	  opr1 = opr1 % opr2;
	  break;
	case '+':
	  opr1 = opr1 + opr2;
	  break;
	case '-':
	  opr1 = opr1 - opr2;
	  break;
	case LEFT_OP:
	  opr1 = opr1 << opr2;
	  break;
	case RIGHT_OP:
	  /* Algorithm right shift.  */
	  opr1 = (offsetT)opr1 >> (offsetT)opr2;
	  break;
	case '<':
	  opr1 = opr1 < opr2;
	  break;
	case '>':
	  opr1 = opr1 > opr2;
	  break;
	case LE_OP:
	  opr1 = opr1 <= opr2;
	  break;
	case GE_OP:
	  opr1 = opr1 >= opr2;
	  break;
	case EQ_OP:
	  opr1 = opr1 == opr2;
	  break;
	case NE_OP:
	  opr1 = opr1 != opr2;
	  break;
	case '&':
	  opr1 = opr1 & opr2;
	  break;
	case '^':
	  opr1 = opr1 ^ opr2;
	  break;
	case '|':
	  opr1 = opr1 | opr2;
	  break;
	case AND_OP:
	  opr1 = opr1 && opr2;
	  break;
	case OR_OP:
	  opr1 = opr1 || opr2;
	  break;
	default:
	  abort ();
	}
      last_2nd->value.X_add_number = opr1;
      last_1st->type = 0;
      top--;
    }
  else
    {
      if (end <= top)
	as_fatal (_("expr too huge"));
      switch (op)
	{
	case '+':
	  top->type = BFD_RELOC_LARCH_SOP_ADD;
	  break;
	case '-':
	  top->type = BFD_RELOC_LARCH_SOP_SUB;
	  break;
	case LEFT_OP:
	  top->type = BFD_RELOC_LARCH_SOP_SL;
	  break;
	case RIGHT_OP:
	  top->type = BFD_RELOC_LARCH_SOP_SR;
	  break;
	case '&':
	  top->type = BFD_RELOC_LARCH_SOP_AND;
	  break;
	default:
	  abort ();
	}
      top->value = const_0;
      top++;
    }
}

static void
emit_if_else (void)
{
  struct reloc_info *last_1st = top - 1;
  struct reloc_info *last_2nd = top - 2;
  struct reloc_info *last_3rd = top - 3;
  if (is_const (last_1st) && is_const (last_2nd) && is_const (last_3rd))
    {
      offsetT opr1 = last_3rd->value.X_add_number;
      offsetT opr2 = last_2nd->value.X_add_number;
      offsetT opr3 = last_1st->value.X_add_number;
      opr1 = opr1 ? opr2 : opr3;
      last_3rd->value.X_add_number = opr1;
      last_2nd->type = 0;
      last_1st->type = 0;
      top -= 2;
    }
  else
    {
      if (end <= top)
	as_fatal (_("expr too huge"));
      top->type = BFD_RELOC_LARCH_SOP_IF_ELSE;
      top->value = const_0;
      top++;
    }
}

%}

%union {
char *c_str;
offsetT imm;
}

%token <imm> INTEGER
%token <c_str> IDENTIFIER
%type <imm> addend

%token LEFT_OP RIGHT_OP LE_OP GE_OP EQ_OP NE_OP AND_OP OR_OP
%start expression
%%

primary_expression
	: INTEGER {emit_const ($1);}
	| '(' expression ')'
	| '%' IDENTIFIER '(' IDENTIFIER addend ')' {reloc ($2, $4, $5); free ($2); free ($4);}
	| '%' IDENTIFIER '(' INTEGER addend ')' {reloc ($2, NULL, $4 + $5); free ($2);}
	;

addend
	: addend '-' INTEGER {$$ -= $3;}
	| addend '+' INTEGER {$$ += $3;}
	| {$$ = 0;}
	;

unary_expression
	: primary_expression
	| '+' unary_expression {emit_unary ('+');}
	| '-' unary_expression {emit_unary ('-');}
	| '~' unary_expression {emit_unary ('~');}
	| '!' unary_expression {emit_unary ('!');}
	;

multiplicative_expression
	: unary_expression
	| multiplicative_expression '*' unary_expression {emit_bin ('*');}
	| multiplicative_expression '/' unary_expression {emit_bin ('/');}
	| multiplicative_expression '%' unary_expression {emit_bin ('%');}
	;

additive_expression
	: multiplicative_expression
	| additive_expression '+' multiplicative_expression {emit_bin ('+');}
	| additive_expression '-' multiplicative_expression {emit_bin ('-');}
	;

shift_expression
	: additive_expression
	| shift_expression LEFT_OP additive_expression {emit_bin (LEFT_OP);}
	| shift_expression RIGHT_OP additive_expression {emit_bin (RIGHT_OP);}
	;

relational_expression
	: shift_expression
	| relational_expression '<' shift_expression {emit_bin ('<');}
	| relational_expression '>' shift_expression {emit_bin ('>');}
	| relational_expression LE_OP shift_expression {emit_bin (LE_OP);}
	| relational_expression GE_OP shift_expression {emit_bin (GE_OP);}
	;

equality_expression
	: relational_expression
	| equality_expression EQ_OP relational_expression {emit_bin (EQ_OP);}
	| equality_expression NE_OP relational_expression {emit_bin (NE_OP);}
	;

and_expression
	: equality_expression
	| and_expression '&' equality_expression {emit_bin ('&');}
	;

exclusive_or_expression
	: and_expression
	| exclusive_or_expression '^' and_expression {emit_bin ('^');}
	;

inclusive_or_expression
	: exclusive_or_expression
	| inclusive_or_expression '|' exclusive_or_expression {emit_bin ('|');}
	;

logical_and_expression
	: inclusive_or_expression
	| logical_and_expression AND_OP inclusive_or_expression {emit_bin (AND_OP);}
	;

logical_or_expression
	: logical_and_expression
	| logical_or_expression OR_OP logical_and_expression {emit_bin (OR_OP);}
	;

conditional_expression
	: logical_or_expression
	| logical_or_expression '?' expression ':' conditional_expression {emit_if_else ();}
	;

expression
	: conditional_expression
	;
%%