/* ------------------ mix_scanner.l :
 * Lexical scanner used by mix_parser_t
 * ------------------------------------------------------------------
 * Copyright (C) 2000 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 2 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, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 *  
 */

%{
#include <string.h>

#include "mix.h"
#include "xmix_parser.h"

#define YY_DECL mix_parser_err_t mix_flex_scan (mix_parser_t *parser)

#define RESET()					\
  do {						\
    mix_ins_fill_from_id (ins, mix_NOP);	\
    ins.address = 0;				\
    ins.index = 0;				\
    nof = FALSE;				\
    lsf = FALSE;				\
    if (symbol != NULL )			\
     {						\
       g_free (symbol);				\
       symbol = NULL;				\
     }						\
    if (lsymbol != NULL)			\
     {						\
       g_free (lsymbol);			\
       lsymbol = NULL;				\
     }                       			\
  } while (FALSE)

#define NEXT()						\
  do {							\
    if (lsymbol != NULL)				\
       mix_parser_manage_local_symbol (parser,lsymbol,  \
				       loc);    	\
    parser->loc_count++;				\
    RESET ();						\
    ++lineno;						\
    BEGIN (INITIAL);					\
  } while (FALSE)

#define ADD_INS()				\
  do {						\
    mix_parser_add_ins (parser, &ins, lineno);	\
    NEXT ();					\
  } while (FALSE)

#define ADD_RAW(value)				\
  do {						\
    mix_parser_add_raw (parser, value, lineno);	\
    NEXT ();	               			\
  } while (FALSE)


#define ENTER_EVAL()				\
  do {						\
    if (yytext[0] != '*')                       \
      {                                         \
        expr_val = MIX_WORD_ZERO;		\
        yyless (0);				\
      }                                         \
    else                                        \
      {                                         \
        expr_val = mix_short_to_word_fast (parser->loc_count); \
	yyless (1);                             \
      }                                         \
    yy_push_state (EVAL);			\
  } while (FALSE)

#define ENTER_WEVAL()				\
  do {						\
    wexpr_val = MIX_WORD_ZERO;			\
    wexpr_val_tmp = MIX_WORD_ZERO;		\
    is_fp = FALSE;                              \
    yyless (0);					\
    yy_push_state (WEVAL);			\
  } while (FALSE)

#define RETURN_ERROR(error, comment)				\
  do {								\
    char c;							\
    mix_parser_log_error (parser,error,lineno,comment,FALSE);	\
    while ( (c = input ()) != '\n' && c != EOF ) ;		\
    if ( c == EOF ) return error; else ++lineno;		\
    RESET ();							\
    BEGIN (INITIAL);						\
  } while (FALSE)

 
static mix_word_t
  eval_binop_ (const gchar *op, mix_word_t x, mix_word_t y);
 
static void
  unput_word_ (mix_word_t word);
 
%}

%option nomain
%option caseless
%option pointer
%option stack
%option noyywrap
%option noyy_top_state
%option noreject
%option outfile="lex.yy.c"

%s  LOC
%s  OP
%s  ADDRESS
%s  INDEX
%s  FSPEC
%s  EVAL
%s  WEVAL
%s  ORIG
%s  CON
%s  EQU
%s  END

ws	[ \t]+
digit	[0-9]
letter	[A-Z]
number	[+-]?{digit}+
mixchar [0-9A-Z .,'')(+*/=$<>@;:\-]
locsymbol   {digit}H
flocsymbol  {digit}F
blocsymbol  {digit}B
symbol	{digit}*{letter}+[A-Z0-9]*
binop	"+"|"-"|"*"|"/"|"//"|":"
atexpr	{digit}+|{symbol}|\*
expr	[+-]?{atexpr}({binop}{1}{atexpr})*
fpart   \({expr}\)
wexpr   {expr}({fpart})?(,{expr}({fpart})?)*
  

%%

%{

  mix_ins_t ins;
  gboolean nof = FALSE, is_fp = FALSE, end = FALSE, lsf = FALSE;
  mix_word_t expr_val = MIX_WORD_ZERO, wexpr_val = MIX_WORD_ZERO,
  wexpr_val_tmp = MIX_WORD_ZERO;
  gchar *symbol = NULL, *lsymbol = NULL;
  mix_address_t loc = MIX_SHORT_ZERO;
  guint lineno = 1;
  mix_ins_fill_from_id (ins, mix_NOP);		
  ins.address = 0;				
  ins.index = 0;				
  parser->err_line = 0;
  yyin = mix_file_to_FILE (parser->in_file);
  
%}
  

<*><<EOF>> {
  mix_parser_log_error (parser, MIX_PERR_UNEX_EOF, lineno, NULL, FALSE);
  return MIX_PERR_UNEX_EOF;
}

<INITIAL>{
  ^\*.*	/* eat comments */
  .	{
    if ( end ) return parser->status;
    yyless (0);
    BEGIN (LOC);
  }
  \n { 
    ++lineno;
    if ( end ) return parser->status; 
  }
}			

<LOC>{
  {ws}+	BEGIN (OP); /* LOC field is empty */
  {locsymbol} { /* manage local symbol */
    loc = get_ploc_ (parser);
    lsymbol = g_strdup (yytext);
    if ( lsymbol == NULL ) {
      mix_parser_log_error (parser, MIX_PERR_INTERNAL, lineno, NULL, FALSE);
      return MIX_PERR_INTERNAL;
    }
    BEGIN (OP);
  }
  {locsymbol}/({ws}+EQU) {/* local symbol with value */
    loc = get_ploc_ (parser);
    symbol = g_strdup (yytext);
    lsymbol = g_strdup (yytext);
    if ( symbol == NULL || lsymbol == NULL) {
      mix_parser_log_error (parser, MIX_PERR_INTERNAL, lineno, NULL, FALSE);
      return MIX_PERR_INTERNAL;
    }
    symbol[1] = 'B'; /* this will be referred as nB afterwards */
    BEGIN (OP);
  }    
  {flocsymbol}|{blocsymbol} RETURN_ERROR (MIX_PERR_UNEX_LOC, yytext);
  {symbol}/({ws}+EQU) { /* store symbol name for future definition */
    symbol = g_strdup (yytext);
    if ( symbol == NULL ) {
      mix_parser_log_error (parser, MIX_PERR_INTERNAL, lineno, NULL, FALSE);
      return MIX_PERR_INTERNAL;
    }
    BEGIN (OP);
  }
  {symbol} { /* define a new symbol */
    mix_parser_err_t err;
    if ( mix_get_id_from_string (yytext) != mix_INVALID_INS )
      mix_parser_log_error (parser, MIX_PERR_SYM_INS, lineno, yytext, TRUE);
    if ( (err = mix_parser_define_symbol_here (parser,yytext)) != MIX_PERR_OK )
      mix_parser_log_error (parser, err, lineno, yytext, FALSE);
    BEGIN (OP);
  }
  .	RETURN_ERROR (MIX_PERR_INV_LOC, yytext);
  \n 	++lineno; /* empty line */
}

<OP>{
  {ws} /* eat leading whitespace */
  \n    RETURN_ERROR (MIX_PERR_NOOP, NULL);
  ORIG{ws}+  BEGIN (ORIG);
  CON{ws}+   BEGIN (CON);
  EQU{ws}+   BEGIN (EQU);
  END{ws}+   BEGIN (END);
  ALF{ws}+\"{mixchar}{5,}\"{ws}+.*\n |
  ALF{ws}+\"{mixchar}{5,}\"{ws}*\n {
    mix_byte_t bytes[5];
    mix_word_t value;
    guint k, j = 4;

    while ( yytext[j++] != '\"' ) ;
    for ( k = j; k < 5+j && yytext[k] != '\"'; ++k )
      bytes[k-j] = mix_ascii_to_char (yytext[k]);
    if ( k-j < 5 )
      mix_parser_log_error (parser, MIX_PERR_SHORT_ALF, lineno, NULL, TRUE);
    else if ( yytext[k] != '\"' )
      mix_parser_log_error (parser, MIX_PERR_LONG_ALF, lineno, NULL, TRUE);

    value = mix_bytes_to_word (bytes, 5);
    
    ADD_RAW (value);
   }
  [A-Z0-9]+{ws}*/\n |    
  [A-Z0-9]+{ws}+ {
    mix_ins_id_t id = mix_get_id_from_string (g_strchomp (yytext));
    if ( id == mix_INVALID_INS )
      mix_parser_log_error (parser, MIX_PERR_INV_OP, lineno, yytext, FALSE);
    else {
      mix_ins_fill_from_id (ins, id);
      nof = mix_ins_id_is_extended (id);
    }
    BEGIN (ADDRESS);
  }
  {expr} RETURN_ERROR (MIX_PERR_INV_OP, yytext);
  .  RETURN_ERROR (MIX_PERR_INV_OP, yytext);		
}


<ORIG>{
  {number}{ws}*\n   |
  {number}{ws}+.*\n {
    mix_word_t value = mix_word_new (atol (yytext));
    parser->loc_count = mix_word_to_short_fast (value);
    ++lineno;
    BEGIN (INITIAL);
  }
}

<CON>{
  {number}{ws}*\n |
  {number}{ws}+.*\n  {
    mix_word_t value = mix_word_new (atol (yytext));
    mix_word_to_ins_uncheck (value, ins);
    ADD_INS ();
  }
}

<EQU>{
  {number}{ws}*\n   |
  {number}{ws}+.*\n {
    mix_word_t value;
    gint def;
    if ( symbol == NULL ) RETURN_ERROR (MIX_PERR_MIS_SYM, NULL);
    value = mix_word_new (atol (yytext));
    def = mix_parser_define_symbol_value (parser, symbol, value);
    if ( def == MIX_SYM_DUP )  RETURN_ERROR (MIX_PERR_DUP_SYMBOL, symbol);
    if ( def == MIX_SYM_LONG )  RETURN_ERROR (MIX_PERR_LONG_SYMBOL, symbol);
    ++lineno;
    BEGIN (INITIAL);
  }
}

<END>{
  {number}{ws}*\n   |
  {number}{ws}+.*\n {
    parser->start = mix_short_new (atol (yytext));
    end = TRUE;
    if ( parser->status == MIX_PERR_NOCOMP ) parser->status = MIX_PERR_OK;
    return parser->status;
  }
}

<ORIG,CON,EQU,END>{
  {wexpr} ENTER_WEVAL ();
  . RETURN_ERROR (MIX_PERR_INV_OP, yytext);
}


<ADDRESS,INDEX,FSPEC>{locsymbol}  RETURN_ERROR (MIX_PERR_UNEX_LOC, yytext);

<ADDRESS>{
  =/[+-]?{number}=  lsf = TRUE;
  =/{expr}=     lsf = TRUE;
  [+-]?{number}={ws}*\n   |
  [+-]?{number}={ws}+.*\n {
    if (!lsf) RETURN_ERROR (MIX_PERR_INV_ADDRESS, yytext);
    mix_parser_define_ls (parser, mix_word_new (atol (yytext)));
    ADD_INS ();
  }
  [+-]?{number}{ws}+.*\n |
  [+-]?{number}[(,\n] {
    ins.address = mix_short_new (atol (yytext));
    switch ( yytext[yyleng-1] ) {
    case '(' : BEGIN (FSPEC); break; 
    case ',' : BEGIN (INDEX); break;
    case '\n' : ADD_INS (); break;
    default: g_assert_not_reached ();
    }
  }
  ([+-]?{symbol})/[(,\n\t ] {
    gboolean neg = (yytext[0] == '-');
    const gchar *s = (neg || yytext[0] == '+')? yytext+1 : yytext;
    if ( !mix_symbol_table_is_defined (parser->symbol_table, s) )
      {
	mix_parser_set_future_ref (parser, s);
	if (neg)
	  mix_parser_log_error (parser, MIX_PERR_UNDEF_SYM, lineno, s, TRUE);
	unput (neg? '1':'0');
      }
    else
      {
	mix_word_t v = mix_symbol_table_value (parser->symbol_table, s);
	if ( neg ) mix_word_reverse_sign (v);
	unput_word_ (v);
      }
  }
  {expr}/[(,=\n\t ] ENTER_EVAL ();
  \n    ADD_INS ();
  .	RETURN_ERROR (MIX_PERR_INV_ADDRESS, yytext);
}
	

<INDEX>{
  {number}[\n(\t ] {
    int end = yytext[yyleng-1];
    ins.index = mix_byte_new (atol (yytext));
    if ( end == '\n' )
      ADD_INS ();
    else if ( end == '(' )
      BEGIN (FSPEC);
    else
      { /* eat rest of line (comment) */
	while ( (end = input()) != '\n' && end != EOF ) ;
	if ( end == '\n' ) ADD_INS ();
	else RETURN_ERROR (MIX_PERR_UNEX_EOF, NULL);
      }
  }
  {expr}/[\n(\t ]  ENTER_EVAL ();
  \n	{
    mix_parser_log_error (parser, MIX_PERR_INV_IDX, lineno++, NULL, FALSE);
    RESET (); 
    BEGIN (INITIAL);
  }
  .	RETURN_ERROR (MIX_PERR_INV_IDX, yytext);
}

<FSPEC>{
  {number}")"(({ws}+.*\n)|\n) {
    glong val  = atol (yytext);
    if ( val < 0 || val > MIX_BYTE_MAX ) 
      RETURN_ERROR (MIX_PERR_INV_FSPEC, NULL);
    if ( ins.opcode != mix_opMOVE 
         && ( ins.opcode < mix_opJBUS || ins.opcode > mix_opJXx )
         && !mix_fspec_is_valid (mix_byte_new (val)) )
      RETURN_ERROR (MIX_PERR_INV_FSPEC, NULL);
    if ( nof ) 
      mix_parser_log_error (parser, MIX_PERR_INV_FSPEC,
			    lineno, _("ignored"), TRUE);
    else
      {
	ins.fspec = mix_byte_new (val);
	ADD_INS ();
      }
  }
  {expr}/")"  {
    ENTER_EVAL ();
  }
  .	RETURN_ERROR (MIX_PERR_INV_FSPEC, yytext);
}


<EVAL>{
  {binop}{digit}+ {
    const gchar *s = ( yytext[1] == '/' ) ? yytext+2 : yytext+1;
    mix_word_t value = mix_word_new (atol (s));
    expr_val = eval_binop_ (yytext, expr_val, value);
  }
  {binop}{symbol} {
    const gchar *s = ( yytext[1] == '/' ) ? yytext+2 : yytext+1;
    if ( !mix_symbol_table_is_defined (parser->symbol_table, s) ) {
      mix_parser_log_error (parser, MIX_PERR_UNDEF_SYM, lineno, s, FALSE);
      yy_pop_state ();
    }
    expr_val = eval_binop_ (yytext, expr_val, 
			    mix_symbol_table_value (parser->symbol_table, s));
  }
  {binop}"*" {
    expr_val = eval_binop_ (yytext, expr_val, 
			    mix_short_to_word_fast (parser->loc_count));
  }
  "*"         unput_word_ (mix_short_to_word_fast (parser->loc_count));
  {number}    expr_val = mix_word_new (atol (yytext));
  {symbol} {
    if ( !mix_symbol_table_is_defined (parser->symbol_table, yytext) ) {
      mix_parser_log_error (parser, MIX_PERR_UNDEF_SYM, lineno, yytext, FALSE);
      yy_pop_state ();
    }
    expr_val = mix_symbol_table_value (parser->symbol_table, yytext);
  }
  [,)(=\n\t ]   unput (yytext[0]); unput_word_ (expr_val); yy_pop_state ();
  .   RETURN_ERROR (MIX_PERR_INV_EXPR, yytext);
}

<WEVAL>{
  {number}"(" {
    is_fp = TRUE;
    wexpr_val_tmp = mix_word_new (atol (yytext));
  }
  {number}")"  {
    glong val  = atol (yytext);
    if ( !is_fp ) {
      mix_parser_log_error (parser, MIX_PERR_MIS_PAREN, lineno, NULL, FALSE);
      yy_pop_state ();
    }
    if ( val < 0 || val > MIX_BYTE_MAX 
         || !mix_fspec_is_valid (mix_byte_new (val)) ) {
      mix_parser_log_error (parser, MIX_PERR_INV_FSPEC, lineno, NULL, FALSE);
      yy_pop_state ();
    }
    is_fp = FALSE;
    wexpr_val = mix_word_store_field (mix_byte_new (val), wexpr_val_tmp, 
				      wexpr_val);
  }
  {number}/[,()\n\t ] wexpr_val = mix_word_new (atol (yytext));
  {expr}/[,()\n\t ]  ENTER_EVAL ();
  ,/{expr} /* eat comma if followed by expression */
  [\n\t ] { /* ok if not inside an f-part */
    if ( is_fp ) {
      mix_parser_log_error (parser, MIX_PERR_MIS_PAREN, lineno, NULL, FALSE);
      yy_pop_state ();
     }
    unput (yytext[yyleng-1]);
    unput_word_ (wexpr_val);
    yy_pop_state ();
  }
  .  RETURN_ERROR (MIX_PERR_INV_EXPR, NULL);
}

%%

static mix_word_t
eval_binop_ (const gchar *op, mix_word_t x, mix_word_t y)
{
  mix_word_t result = MIX_WORD_ZERO;
  switch (op[0])
    {
    case '+': 
      result = mix_word_add (x,y); 
      break;
    case '-':
      result = mix_word_sub (x,y);
      break;
    case '*':
      mix_word_mul (x, y, NULL, &result);
      break;
    case ':':
      {
	mix_word_t a;
	mix_word_mul (x, 8, NULL, &a);
	result = mix_word_add (a, y);
	break;
      }
    case '/':
      if ( strlen (op) > 1 && op[1] == '/' ) {
	mix_word_div (x,MIX_WORD_ZERO,y, &result, NULL);
      } else {
	mix_word_div (MIX_WORD_ZERO, x, y, &result, NULL);
      }
      break;
    default:
      g_assert_not_reached ();
    }
  return result;
}


static void
unput_word_ (mix_word_t word)					
{								
  gchar *value;						
  gint k;							
  value = g_strdup_printf ("%s%ld",				
			   mix_word_is_negative (word)? "-":"+",
			   mix_word_magnitude (word));		
  for (k = strlen (value) - 1; k >= 0; --k)			
    unput (value[k]);						
  g_free (value);						
}