%{
/*
 *  Copyright(c) 2019-2022 rev.ng Labs Srl. All Rights Reserved.
 *
 *  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 <http://www.gnu.org/licenses/>.
 */

#include "idef-parser.h"
#include "parser-helpers.h"
#include "idef-parser.tab.h"
#include "idef-parser.yy.h"

/* Uncomment this to disable yyasserts */
/* #define NDEBUG */

#define ERR_LINE_CONTEXT 40

%}

%lex-param {void *scanner}
%parse-param {void *scanner}
%parse-param {Context *c}

%define parse.error verbose
%define parse.lac full
%define api.pure full

%locations

%union {
    GString *string;
    HexValue rvalue;
    HexSat sat;
    HexCast cast;
    HexExtract extract;
    HexMpy mpy;
    HexSignedness signedness;
    int index;
}

/* Tokens */
%start input

%expect 1

%token IN INAME VAR
%token ABS CROUND ROUND CIRCADD COUNTONES INC DEC ANDA ORA XORA PLUSPLUS ASL
%token ASR LSR EQ NEQ LTE GTE MIN MAX ANDL FOR ICIRC IF MUN FSCR FCHK SXT
%token ZXT CONSTEXT LOCNT BREV SIGN LOAD STORE PC NPC LPCFG
%token LOAD_CANCEL CANCEL IDENTITY PART1 ROTL INSBITS SETBITS EXTRANGE
%token CAST4_8U FAIL CARRY_FROM_ADD ADDSAT64 LSBNEW
%token TYPE_SIZE_T TYPE_INT TYPE_SIGNED TYPE_UNSIGNED TYPE_LONG

%token <rvalue> REG IMM PRED
%token <index> ELSE
%token <mpy> MPY
%token <sat> SAT
%token <cast> CAST DEPOSIT SETHALF
%token <extract> EXTRACT
%type <string> INAME
%type <rvalue> rvalue lvalue VAR assign_statement var var_decl var_type
%type <rvalue> FAIL
%type <rvalue> TYPE_SIGNED TYPE_UNSIGNED TYPE_INT TYPE_LONG TYPE_SIZE_T
%type <index> if_stmt IF
%type <signedness> SIGN

/* Operator Precedences */
%left MIN MAX
%left '('
%left ','
%left '='
%right CIRCADD
%right INC DEC ANDA ORA XORA
%left '?' ':'
%left ANDL
%left '|'
%left '^' ANDOR
%left '&'
%left EQ NEQ
%left '<' '>' LTE GTE
%left ASL ASR LSR
%right ABS
%left '-' '+'
%left '*' '/' '%' MPY
%right '~' '!'
%left '['
%right CAST
%right LOCNT BREV

/* Bison Grammar */
%%

/* Input file containing the description of each hexagon instruction */
input : instructions
      {
          /* Suppress warning about unused yynerrs */
          (void) yynerrs;
          YYACCEPT;
      }
      ;

instructions : instruction instructions
             | %empty
             ;

instruction : INAME
              {
                  gen_inst(c, $1);
              }
              arguments
              {
                  EMIT_SIG(c, ")");
                  EMIT_HEAD(c, "{\n");
              }
              code
              {
                  gen_inst_code(c, &@1);
              }
            | error /* Recover gracefully after instruction compilation error */
              {
                  free_instruction(c);
              }
            ;

arguments : '(' ')'
          | '(' argument_list ')';

argument_list : argument_decl ',' argument_list
              | argument_decl
              ;

var : VAR
      {
          track_string(c, $1.var.name);
          $$ = $1;
      }
    ;

/*
 * Here the integer types are defined from valid combinations of
 * `signed`, `unsigned`, `int`, and `long` tokens. The `signed`
 * and `unsigned` tokens are here assumed to always be placed
 * first in the type declaration, which is not the case in
 * normal C. Similarly, `int` is assumed to always be placed
 * last in the type.
 */
type_int : TYPE_INT
         | TYPE_SIGNED
         | TYPE_SIGNED TYPE_INT;
type_uint : TYPE_UNSIGNED
          | TYPE_UNSIGNED TYPE_INT;
type_ulonglong : TYPE_UNSIGNED TYPE_LONG TYPE_LONG
               | TYPE_UNSIGNED TYPE_LONG TYPE_LONG TYPE_INT;

/*
 * Here the various valid int types defined above specify
 * their `signedness` and `bit_width`. The LP64 convention
 * is assumed where longs are 64-bit, long longs are then
 * assumed to also be 64-bit.
 */
var_type : TYPE_SIZE_T
           {
              yyassert(c, &@1, $1.bit_width <= 64,
                       "Variables with size > 64-bit are not supported!");
              $$ = $1;
           }
         | type_int
           {
              $$.signedness = SIGNED;
              $$.bit_width  = 32;
           }
         | type_uint
           {
              $$.signedness = UNSIGNED;
              $$.bit_width  = 32;
           }
         | type_ulonglong
           {
              $$.signedness = UNSIGNED;
              $$.bit_width  = 64;
           }
         ;

/* Rule to capture declarations of VARs */
var_decl : var_type IMM
           {
              /*
               * Rule to capture "int i;" declarations since "i" is special
               * and assumed to be always be IMM. Moreover, "i" is only
               * assumed to be used in for-loops.
               *
               * Therefore we want to NOP these declarations.
               */
              yyassert(c, &@2, $2.imm.type == I,
                       "Variable declaration with immedaties only allowed"
                       " for the loop induction variable \"i\"");
              $$ = $2;
           }
         | var_type var
           {
              /*
               * Allocate new variable, this checks that it hasn't already
               * been declared.
               */
              gen_varid_allocate(c, &@1, &$2, $1.bit_width, $1.signedness);
              /* Copy var for variable name */
              $$ = $2;
              /* Copy type info from var_type */
              $$.signedness = $1.signedness;
              $$.bit_width  = $1.bit_width;
           }
         ;

/* Return the modified registers list */
code : '{' statements '}'
       {
           c->inst.code_begin = c->input_buffer + @2.first_column - 1;
           c->inst.code_end = c->input_buffer + @2.last_column - 1;
       }
     | '{'
       {
           /* Nop */
       }
       '}'
     ;

argument_decl : REG
                {
                    emit_arg(c, &@1, &$1);
                    /* Enqueue register into initialization list */
                    g_array_append_val(c->inst.init_list, $1);
                }
              | PRED
                {
                    emit_arg(c, &@1, &$1);
                    /* Enqueue predicate into initialization list */
                    g_array_append_val(c->inst.init_list, $1);
                }
              | IN REG
                {
                    emit_arg(c, &@2, &$2);
                }
              | IN PRED
                {
                    emit_arg(c, &@2, &$2);
                }
              | IMM
                {
                    EMIT_SIG(c, ", int %ciV", $1.imm.id);
                }
              ;

code_block : '{' statements '}'
           | '{' '}'
           ;

/* A list of one or more statements */
statements : statements statement
           | statement
           ;

/* Statements can be assignment (rvalue ';'), control or memory statements */
statement : control_statement
          | var_decl ';'
          | rvalue ';'
            {
                gen_rvalue_free(c, &@1, &$1);
            }
          | code_block
          | ';'
          ;

assign_statement : lvalue '=' rvalue
                   {
                       @1.last_column = @3.last_column;
                       gen_assign(c, &@1, &$1, &$3);
                       $$ = $1;
                   }
                 | var_decl '=' rvalue
                   {
                       @1.last_column = @3.last_column;
                       gen_assign(c, &@1, &$1, &$3);
                       $$ = $1;
                   }
                 | lvalue INC rvalue
                   {
                       @1.last_column = @3.last_column;
                       HexValue tmp = gen_bin_op(c, &@1, ADD_OP, &$1, &$3);
                       gen_assign(c, &@1, &$1, &tmp);
                       $$ = $1;
                   }
                 | lvalue DEC rvalue
                   {
                       @1.last_column = @3.last_column;
                       HexValue tmp = gen_bin_op(c, &@1, SUB_OP, &$1, &$3);
                       gen_assign(c, &@1, &$1, &tmp);
                       $$ = $1;
                   }
                 | lvalue ANDA rvalue
                   {
                       @1.last_column = @3.last_column;
                       HexValue tmp = gen_bin_op(c, &@1, ANDB_OP, &$1, &$3);
                       gen_assign(c, &@1, &$1, &tmp);
                       $$ = $1;
                   }
                 | lvalue ORA rvalue
                   {
                       @1.last_column = @3.last_column;
                       HexValue tmp = gen_bin_op(c, &@1, ORB_OP, &$1, &$3);
                       gen_assign(c, &@1, &$1, &tmp);
                       $$ = $1;
                   }
                 | lvalue XORA rvalue
                   {
                       @1.last_column = @3.last_column;
                       HexValue tmp = gen_bin_op(c, &@1, XORB_OP, &$1, &$3);
                       gen_assign(c, &@1, &$1, &tmp);
                       $$ = $1;
                   }
                 | PRED '=' rvalue
                   {
                       @1.last_column = @3.last_column;
                       gen_pred_assign(c, &@1, &$1, &$3);
                   }
                 | IMM '=' rvalue
                   {
                       @1.last_column = @3.last_column;
                       yyassert(c, &@1, $3.type == IMMEDIATE,
                                "Cannot assign non-immediate to immediate!");
                       yyassert(c, &@1, $1.imm.type == VARIABLE,
                                "Cannot assign to non-variable!");
                       /* Assign to the function argument */
                       OUT(c, &@1, &$1, " = ", &$3, ";\n");
                       $$ = $1;
                   }
                 | PC '=' rvalue
                   {
                       @1.last_column = @3.last_column;
                       yyassert(c, &@1, !is_inside_ternary(c),
                                "Assignment side-effect not modeled!");
                       $3 = gen_rvalue_truncate(c, &@1, &$3);
                       $3 = rvalue_materialize(c, &@1, &$3);
                       OUT(c, &@1, "gen_write_new_pc(", &$3, ");\n");
                       gen_rvalue_free(c, &@1, &$3); /* Free temporary value */
                   }
                 | LOAD '(' IMM ',' IMM ',' SIGN ',' var ',' lvalue ')'
                   {
                       @1.last_column = @12.last_column;
                       yyassert(c, &@1, !is_inside_ternary(c),
                                "Assignment side-effect not modeled!");
                       yyassert(c, &@1, $3.imm.value == 1,
                                "LOAD of arrays not supported!");
                       gen_load(c, &@1, &$5, $7, &$9, &$11);
                   }
                 | STORE '(' IMM ',' IMM ',' var ',' rvalue ')'
                   /* Store primitive */
                   {
                       @1.last_column = @10.last_column;
                       yyassert(c, &@1, !is_inside_ternary(c),
                                "Assignment side-effect not modeled!");
                       yyassert(c, &@1, $3.imm.value == 1,
                                "STORE of arrays not supported!");
                       gen_store(c, &@1, &$5, &$7, &$9);
                   }
                 | LPCFG '=' rvalue
                   {
                       @1.last_column = @3.last_column;
                       yyassert(c, &@1, !is_inside_ternary(c),
                                "Assignment side-effect not modeled!");
                       $3 = gen_rvalue_truncate(c, &@1, &$3);
                       $3 = rvalue_materialize(c, &@1, &$3);
                       OUT(c, &@1, "SET_USR_FIELD(USR_LPCFG, ", &$3, ");\n");
                       gen_rvalue_free(c, &@1, &$3);
                   }
                 | DEPOSIT '(' rvalue ',' rvalue ',' rvalue ')'
                   {
                       @1.last_column = @8.last_column;
                       yyassert(c, &@1, !is_inside_ternary(c),
                                "Assignment side-effect not modeled!");
                       gen_deposit_op(c, &@1, &$5, &$7, &$3, &$1);
                   }
                 | SETHALF '(' rvalue ',' lvalue ',' rvalue ')'
                   {
                       @1.last_column = @8.last_column;
                       yyassert(c, &@1, !is_inside_ternary(c),
                                "Assignment side-effect not modeled!");
                       gen_sethalf(c, &@1, &$1, &$3, &$5, &$7);
                   }
                 | SETBITS '(' rvalue ',' rvalue ',' rvalue ',' rvalue ')'
                   {
                       @1.last_column = @10.last_column;
                       yyassert(c, &@1, !is_inside_ternary(c),
                                "Assignment side-effect not modeled!");
                       gen_setbits(c, &@1, &$3, &$5, &$7, &$9);
                   }
                 | INSBITS '(' lvalue ',' rvalue ',' rvalue ',' rvalue ')'
                   {
                       @1.last_column = @10.last_column;
                       yyassert(c, &@1, !is_inside_ternary(c),
                                "Assignment side-effect not modeled!");
                       gen_rdeposit_op(c, &@1, &$3, &$9, &$7, &$5);
                   }
                 | IDENTITY '(' rvalue ')'
                   {
                       @1.last_column = @4.last_column;
                       $$ = $3;
                   }
                 ;

control_statement : frame_check
                  | cancel_statement
                  | if_statement
                  | for_statement
                  | fpart1_statement
                  ;

frame_check : FCHK '(' rvalue ',' rvalue ')' ';'
              {
                  gen_rvalue_free(c, &@1, &$3);
                  gen_rvalue_free(c, &@1, &$5);
              }
            ;

cancel_statement : LOAD_CANCEL
                   {
                       gen_load_cancel(c, &@1);
                   }
                 | CANCEL
                   {
                       gen_cancel(c, &@1);
                   }
                 ;

if_statement : if_stmt
               {
                   /* Fix else label */
                   OUT(c, &@1, "gen_set_label(if_label_", &$1, ");\n");
               }
             | if_stmt ELSE
               {
                   @1.last_column = @2.last_column;
                   $2 = gen_if_else(c, &@1, $1);
               }
               statement
               {
                   OUT(c, &@1, "gen_set_label(if_label_", &$2, ");\n");
               }
             ;

for_statement : FOR '(' IMM '=' IMM ';' IMM '<' IMM ';' IMM PLUSPLUS ')'
                {
                    yyassert(c, &@3,
                             $3.imm.type == I &&
                             $7.imm.type == I &&
                             $11.imm.type == I,
                             "Loop induction variable must be \"i\"");
                    @1.last_column = @13.last_column;
                    OUT(c, &@1, "for (int ", &$3, " = ", &$5, "; ",
                        &$7, " < ", &$9);
                    OUT(c, &@1, "; ", &$11, "++) {\n");
                }
                code_block
                {
                    OUT(c, &@1, "}\n");
                }
              ;

fpart1_statement : PART1
                   {
                       OUT(c, &@1, "if (insn->part1) {\n");
                   }
                   '(' statements ')'
                   {
                       @1.last_column = @3.last_column;
                       OUT(c, &@1, "return; }\n");
                   }
                 ;

if_stmt : IF '(' rvalue ')'
          {
              @1.last_column = @3.last_column;
              $1 = gen_if_cond(c, &@1, &$3);
          }
          statement
          {
              $$ = $1;
          }
        ;

rvalue : FAIL
         {
             yyassert(c, &@1, false, "Encountered a FAIL token as rvalue.\n");
         }
       | assign_statement
       | REG
         {
             $$ = $1;
         }
       | IMM
         {
             $$ = $1;
         }
       | PRED
         {
             $$ = gen_rvalue_pred(c, &@1, &$1);
         }
       | PC
         {
             /* Read PC from the CR */
             HexValue rvalue;
             memset(&rvalue, 0, sizeof(HexValue));
             rvalue.type = IMMEDIATE;
             rvalue.imm.type = IMM_PC;
             rvalue.bit_width = 32;
             rvalue.signedness = UNSIGNED;
             $$ = rvalue;
         }
       | NPC
         {
             /*
              * NPC is only read from CALLs, so we can hardcode it
              * at translation time
              */
             HexValue rvalue;
             memset(&rvalue, 0, sizeof(HexValue));
             rvalue.type = IMMEDIATE;
             rvalue.imm.type = IMM_NPC;
             rvalue.bit_width = 32;
             rvalue.signedness = UNSIGNED;
             $$ = rvalue;
         }
       | CONSTEXT
         {
             HexValue rvalue;
             memset(&rvalue, 0, sizeof(HexValue));
             rvalue.type = IMMEDIATE;
             rvalue.imm.type = IMM_CONSTEXT;
             rvalue.signedness = UNSIGNED;
             rvalue.is_dotnew = false;
             rvalue.is_manual = false;
             $$ = rvalue;
         }
       | var
         {
             $$ = gen_rvalue_var(c, &@1, &$1);
         }
       | MPY '(' rvalue ',' rvalue ')'
         {
             @1.last_column = @6.last_column;
             $$ = gen_rvalue_mpy(c, &@1, &$1, &$3, &$5);
         }
       | rvalue '+' rvalue
         {
             @1.last_column = @3.last_column;
             $$ = gen_bin_op(c, &@1, ADD_OP, &$1, &$3);
         }
       | rvalue '-' rvalue
         {
             @1.last_column = @3.last_column;
             $$ = gen_bin_op(c, &@1, SUB_OP, &$1, &$3);
         }
       | rvalue '*' rvalue
         {
             @1.last_column = @3.last_column;
             $$ = gen_bin_op(c, &@1, MUL_OP, &$1, &$3);
         }
       | rvalue ASL rvalue
         {
             @1.last_column = @3.last_column;
             $$ = gen_bin_op(c, &@1, ASL_OP, &$1, &$3);
         }
       | rvalue ASR rvalue
         {
             @1.last_column = @3.last_column;
             assert_signedness(c, &@1, $1.signedness);
             if ($1.signedness == UNSIGNED) {
                 $$ = gen_bin_op(c, &@1, LSR_OP, &$1, &$3);
             } else if ($1.signedness == SIGNED) {
                 $$ = gen_bin_op(c, &@1, ASR_OP, &$1, &$3);
             }
         }
       | rvalue LSR rvalue
         {
             @1.last_column = @3.last_column;
             $$ = gen_bin_op(c, &@1, LSR_OP, &$1, &$3);
         }
       | rvalue '&' rvalue
         {
             @1.last_column = @3.last_column;
             $$ = gen_bin_op(c, &@1, ANDB_OP, &$1, &$3);
         }
       | rvalue '|' rvalue
         {
             @1.last_column = @3.last_column;
             $$ = gen_bin_op(c, &@1, ORB_OP, &$1, &$3);
         }
       | rvalue '^' rvalue
         {
             @1.last_column = @3.last_column;
             $$ = gen_bin_op(c, &@1, XORB_OP, &$1, &$3);
         }
       | rvalue ANDL rvalue
         {
             @1.last_column = @3.last_column;
             $$ = gen_bin_op(c, &@1, ANDL_OP, &$1, &$3);
         }
       | MIN '(' rvalue ',' rvalue ')'
         {
             @1.last_column = @3.last_column;
             $$ = gen_bin_op(c, &@1, MINI_OP, &$3, &$5);
         }
       | MAX '(' rvalue ',' rvalue ')'
         {
             @1.last_column = @3.last_column;
             $$ = gen_bin_op(c, &@1, MAXI_OP, &$3, &$5);
         }
       | '~' rvalue
         {
             @1.last_column = @2.last_column;
             $$ = gen_rvalue_not(c, &@1, &$2);
         }
       | '!' rvalue
         {
             @1.last_column = @2.last_column;
             $$ = gen_rvalue_notl(c, &@1, &$2);
         }
       | SAT '(' IMM ',' rvalue ')'
         {
             @1.last_column = @6.last_column;
             $$ = gen_rvalue_sat(c, &@1, &$1, &$3, &$5);
         }
       | CAST rvalue
         {
             @1.last_column = @2.last_column;
             /* Assign target signedness */
             $2.signedness = $1.signedness;
             $$ = gen_cast_op(c, &@1, &$2, $1.bit_width, $1.signedness);
         }
       | rvalue EQ rvalue
         {
             @1.last_column = @3.last_column;
             $$ = gen_bin_cmp(c, &@1, TCG_COND_EQ, &$1, &$3);
         }
       | rvalue NEQ rvalue
         {
             @1.last_column = @3.last_column;
             $$ = gen_bin_cmp(c, &@1, TCG_COND_NE, &$1, &$3);
         }
       | rvalue '<' rvalue
         {
             @1.last_column = @3.last_column;

             assert_signedness(c, &@1, $1.signedness);
             assert_signedness(c, &@1, $3.signedness);
             if ($1.signedness == UNSIGNED || $3.signedness == UNSIGNED) {
                 $$ = gen_bin_cmp(c, &@1, TCG_COND_LTU, &$1, &$3);
             } else {
                 $$ = gen_bin_cmp(c, &@1, TCG_COND_LT, &$1, &$3);
             }
         }
       | rvalue '>' rvalue
         {
             @1.last_column = @3.last_column;

             assert_signedness(c, &@1, $1.signedness);
             assert_signedness(c, &@1, $3.signedness);
             if ($1.signedness == UNSIGNED || $3.signedness == UNSIGNED) {
                 $$ = gen_bin_cmp(c, &@1, TCG_COND_GTU, &$1, &$3);
             } else {
                 $$ = gen_bin_cmp(c, &@1, TCG_COND_GT, &$1, &$3);
             }
         }
       | rvalue LTE rvalue
         {
             @1.last_column = @3.last_column;

             assert_signedness(c, &@1, $1.signedness);
             assert_signedness(c, &@1, $3.signedness);
             if ($1.signedness == UNSIGNED || $3.signedness == UNSIGNED) {
                 $$ = gen_bin_cmp(c, &@1, TCG_COND_LEU, &$1, &$3);
             } else {
                 $$ = gen_bin_cmp(c, &@1, TCG_COND_LE, &$1, &$3);
             }
         }
       | rvalue GTE rvalue
         {
             @1.last_column = @3.last_column;

             assert_signedness(c, &@1, $1.signedness);
             assert_signedness(c, &@1, $3.signedness);
             if ($1.signedness == UNSIGNED || $3.signedness == UNSIGNED) {
                 $$ = gen_bin_cmp(c, &@1, TCG_COND_GEU, &$1, &$3);
             } else {
                 $$ = gen_bin_cmp(c, &@1, TCG_COND_GE, &$1, &$3);
             }
         }
       | rvalue '?'
         {
             $1.is_manual = true;
             Ternary t = { 0 };
             t.state = IN_LEFT;
             t.cond = $1;
             g_array_append_val(c->ternary, t);
         }
         rvalue ':'
         {
             Ternary *t = &g_array_index(c->ternary, Ternary,
                                         c->ternary->len - 1);
             t->state = IN_RIGHT;
         }
         rvalue
         {
             @1.last_column = @5.last_column;
             $$ = gen_rvalue_ternary(c, &@1, &$1, &$4, &$7);
         }
       | FSCR '(' rvalue ')'
         {
             @1.last_column = @4.last_column;
             $$ = gen_rvalue_fscr(c, &@1, &$3);
         }
       | SXT '(' rvalue ',' IMM ',' rvalue ')'
         {
             @1.last_column = @8.last_column;
             yyassert(c, &@1, $5.type == IMMEDIATE &&
                      $5.imm.type == VALUE,
                      "SXT expects immediate values\n");
             $$ = gen_extend_op(c, &@1, &$3, $5.imm.value, &$7, SIGNED);
         }
       | ZXT '(' rvalue ',' IMM ',' rvalue ')'
         {
             @1.last_column = @8.last_column;
             yyassert(c, &@1, $5.type == IMMEDIATE &&
                      $5.imm.type == VALUE,
                      "ZXT expects immediate values\n");
             $$ = gen_extend_op(c, &@1, &$3, $5.imm.value, &$7, UNSIGNED);
         }
       | '(' rvalue ')'
         {
             $$ = $2;
         }
       | ABS rvalue
         {
             @1.last_column = @2.last_column;
             $$ = gen_rvalue_abs(c, &@1, &$2);
         }
       | CROUND '(' rvalue ',' rvalue ')'
         {
             @1.last_column = @6.last_column;
             $$ = gen_convround_n(c, &@1, &$3, &$5);
         }
       | CROUND '(' rvalue ')'
         {
             @1.last_column = @4.last_column;
             $$ = gen_convround(c, &@1, &$3);
         }
       | ROUND '(' rvalue ',' rvalue ')'
         {
             @1.last_column = @6.last_column;
             $$ = gen_round(c, &@1, &$3, &$5);
         }
       | '-' rvalue
         {
             @1.last_column = @2.last_column;
             $$ = gen_rvalue_neg(c, &@1, &$2);
         }
       | ICIRC '(' rvalue ')' ASL IMM
         {
             @1.last_column = @6.last_column;
             $$ = gen_tmp(c, &@1, 32, UNSIGNED);
             OUT(c, &@1, "gen_read_ireg(", &$$, ", ", &$3, ", ", &$6, ");\n");
             gen_rvalue_free(c, &@1, &$3);
         }
       | CIRCADD '(' rvalue ',' rvalue ',' rvalue ')'
         {
             @1.last_column = @8.last_column;
             gen_circ_op(c, &@1, &$3, &$5, &$7);
         }
       | LOCNT '(' rvalue ')'
         {
             @1.last_column = @4.last_column;
             /* Leading ones count */
             $$ = gen_locnt_op(c, &@1, &$3);
         }
       | COUNTONES '(' rvalue ')'
         {
             @1.last_column = @4.last_column;
             /* Ones count */
             $$ = gen_ctpop_op(c, &@1, &$3);
         }
       | LPCFG
         {
             $$ = gen_tmp_value(c, &@1, "0", 32, UNSIGNED);
             OUT(c, &@1, "GET_USR_FIELD(USR_LPCFG, ", &$$, ");\n");
         }
       | EXTRACT '(' rvalue ',' rvalue ')'
         {
             @1.last_column = @6.last_column;
             $$ = gen_extract_op(c, &@1, &$5, &$3, &$1);
         }
       | EXTRANGE '(' rvalue ',' rvalue ',' rvalue ')'
         {
             @1.last_column = @8.last_column;
             yyassert(c, &@1, $5.type == IMMEDIATE &&
                      $5.imm.type == VALUE &&
                      $7.type == IMMEDIATE &&
                      $7.imm.type == VALUE,
                      "Range extract needs immediate values!\n");
             $$ = gen_rextract_op(c,
                                  &@1,
                                  &$3,
                                  $7.imm.value,
                                  $5.imm.value - $7.imm.value + 1);
         }
       | CAST4_8U '(' rvalue ')'
         {
             @1.last_column = @4.last_column;
             $$ = gen_rvalue_truncate(c, &@1, &$3);
             $$.signedness = UNSIGNED;
             $$ = rvalue_materialize(c, &@1, &$$);
             $$ = gen_rvalue_extend(c, &@1, &$$);
         }
       | BREV '(' rvalue ')'
         {
             @1.last_column = @4.last_column;
             $$ = gen_rvalue_brev(c, &@1, &$3);
         }
       | ROTL '(' rvalue ',' rvalue ')'
         {
             @1.last_column = @6.last_column;
             $$ = gen_rotl(c, &@1, &$3, &$5);
         }
       | ADDSAT64 '(' rvalue ',' rvalue ',' rvalue ')'
         {
             @1.last_column = @8.last_column;
             gen_addsat64(c, &@1, &$3, &$5, &$7);
         }
       | CARRY_FROM_ADD '(' rvalue ',' rvalue ',' rvalue ')'
         {
             @1.last_column = @8.last_column;
             $$ = gen_carry_from_add(c, &@1, &$3, &$5, &$7);
         }
       | LSBNEW '(' rvalue ')'
         {
             @1.last_column = @4.last_column;
             HexValue one = gen_imm_value(c, &@1, 1, 32, UNSIGNED);
             $$ = gen_bin_op(c, &@1, ANDB_OP, &$3, &one);
         }
       ;

lvalue : FAIL
         {
             @1.last_column = @1.last_column;
             yyassert(c, &@1, false, "Encountered a FAIL token as lvalue.\n");
         }
       | REG
         {
             $$ = $1;
         }
       | var
         {
             $$ = $1;
         }
       ;

%%

int main(int argc, char **argv)
{
    if (argc != 5) {
        fprintf(stderr,
                "Semantics: Hexagon ISA to tinycode generator compiler\n\n");
        fprintf(stderr,
                "Usage: ./semantics IDEFS EMITTER_C EMITTER_H "
                "ENABLED_INSTRUCTIONS_LIST\n");
        return 1;
    }

    enum {
        ARG_INDEX_ARGV0 = 0,
        ARG_INDEX_IDEFS,
        ARG_INDEX_EMITTER_C,
        ARG_INDEX_EMITTER_H,
        ARG_INDEX_ENABLED_INSTRUCTIONS_LIST
    };

    FILE *enabled_file = fopen(argv[ARG_INDEX_ENABLED_INSTRUCTIONS_LIST], "w");

    FILE *output_file = fopen(argv[ARG_INDEX_EMITTER_C], "w");
    fputs("#include \"qemu/osdep.h\"\n", output_file);
    fputs("#include \"qemu/log.h\"\n", output_file);
    fputs("#include \"cpu.h\"\n", output_file);
    fputs("#include \"internal.h\"\n", output_file);
    fputs("#include \"tcg/tcg-op.h\"\n", output_file);
    fputs("#include \"insn.h\"\n", output_file);
    fputs("#include \"opcodes.h\"\n", output_file);
    fputs("#include \"translate.h\"\n", output_file);
    fputs("#define QEMU_GENERATE\n", output_file);
    fputs("#include \"genptr.h\"\n", output_file);
    fputs("#include \"tcg/tcg.h\"\n", output_file);
    fputs("#include \"macros.h\"\n", output_file);
    fprintf(output_file, "#include \"%s\"\n", argv[ARG_INDEX_EMITTER_H]);

    FILE *defines_file = fopen(argv[ARG_INDEX_EMITTER_H], "w");
    assert(defines_file != NULL);
    fputs("#ifndef HEX_EMITTER_H\n", defines_file);
    fputs("#define HEX_EMITTER_H\n", defines_file);
    fputs("\n", defines_file);
    fputs("#include \"insn.h\"\n\n", defines_file);

    /* Parser input file */
    Context context = { 0 };
    context.defines_file = defines_file;
    context.output_file = output_file;
    context.enabled_file = enabled_file;
    /* Initialize buffers */
    context.out_str = g_string_new(NULL);
    context.signature_str = g_string_new(NULL);
    context.header_str = g_string_new(NULL);
    context.ternary = g_array_new(FALSE, TRUE, sizeof(Ternary));
    /* Read input file */
    FILE *input_file = fopen(argv[ARG_INDEX_IDEFS], "r");
    fseek(input_file, 0L, SEEK_END);
    long input_size = ftell(input_file);
    context.input_buffer = (char *) calloc(input_size + 1, sizeof(char));
    fseek(input_file, 0L, SEEK_SET);
    size_t read_chars = fread(context.input_buffer,
                              sizeof(char),
                              input_size,
                              input_file);
    if (read_chars != (size_t) input_size) {
        fprintf(stderr, "Error: an error occurred while reading input file!\n");
        return -1;
    }
    yylex_init(&context.scanner);
    YY_BUFFER_STATE buffer;
    buffer = yy_scan_string(context.input_buffer, context.scanner);
    /* Start the parsing procedure */
    yyparse(context.scanner, &context);
    if (context.implemented_insn != context.total_insn) {
        fprintf(stderr,
                "Warning: %d/%d meta instructions have been implemented!\n",
                context.implemented_insn,
                context.total_insn);
    }
    fputs("#endif " START_COMMENT " HEX_EMITTER_h " END_COMMENT "\n",
          defines_file);
    /* Cleanup */
    yy_delete_buffer(buffer, context.scanner);
    yylex_destroy(context.scanner);
    free(context.input_buffer);
    g_string_free(context.out_str, TRUE);
    g_string_free(context.signature_str, TRUE);
    g_string_free(context.header_str, TRUE);
    g_array_free(context.ternary, TRUE);
    fclose(output_file);
    fclose(input_file);
    fclose(defines_file);
    fclose(enabled_file);

    return 0;
}
