%{
#include "mit2mot.config.h"
#define FALSE 0
#define TRUE 1
extern FILE *fd_out;
static void printcomment( const char *s );
static char *CalkRegisters ( long value );
static struct t_worte *KlammerAussenWeg ( struct t_worte *w );
static short PreDecrement = FALSE;
static int Modus=0;
#define DRAUSSEN 1
void SetDraussen(void) { Modus|=DRAUSSEN; }
%}

%union {
char *string;
struct t_number { char *string;
		  long value; } number;
struct t_worte *worte;
}

/* Externe Tokens benennen. Diese Tokens werden von yylex() uebergeben */

%token RETURN
%token <string> BLANK
%token <number> NUM
%token <string> REG
%token <string> IDENT
%token <string> LABEL
%token <number> IMED_ADDRESS
%token <string> COMMENT
%token <string> DIR
%token <string> DIR_FILL
%token <string> DIR_ALIGN
%token <string> DIR_SET
%token <string> MNEMO
%token <string> SIZE
%token <string> FEHLER


/* Interne Regeln benennen. */

/* %type <void> GLOBAL_INPUT INPUT LINE AFFE */
%type <string> EXP
%type <worte>  EXP2
%type <string> ADDR
%type <string> EXP_LST
%type <worte>  EXP_LST2
%type <string> EXP_RST
%type <string> ADDR_LST
%type <string> DIRECT
%type <string> INSTR
/* %type <void> PrintCOMMENT */


/* Prioritaeten der Arithmethischen Ausdruecke festlegen, und die
   Art der Auswertung (von links nach rechts). Ist hier eigentlich
   nicht notwendig, dient aber zur Vermeidung von vielen shift/reduce
   Konflikten */
%left ','
%left '+' '-'
%left '|' '&' '^' '!'
%left '*' '/' '%' '>' '<'
%left '~' NEG
%left ':'
%left '{'

%%
/* GLOBAL_INPUT: Regel zum lesen der Eingabedatei */
GLOBAL_INPUT:  
          INPUT
	| GLOBAL_INPUT RETURN INPUT
        | error RETURN
             { fprintf(fd_out,"parse error, can't convert -> %s <-\n",lexline()); }
          INPUT 
;

INPUT:
          LINE PrintCOMMENT
        ;

/* LINE: Regel zum lesen einer Zeile, hier geschieht die endgueltige
         Ausgabe */
LINE:
	  DIRECT 	{ fprintf(fd_out,"\t%s",$1); } 
	| BLANK INSTR   { fprintf(fd_out,"\t%s",$2); } 
        | LABEL	        { fprintf(fd_out,"%s",$1); }
        | LABEL BLANK INSTR
                        { fprintf(fd_out,"%s\t%s",$1,$3); }
	| /* empty */   { ; }
;

/* PrintCOMMENT: Regeln zum lesen eines Kommentares. */
PrintCOMMENT:
	/* empty */	{ fprintf(fd_out,"\n");
			  lexnextline();
			  cprintfclear();
			  PreDecrement = FALSE; }
	| COMMENT	{ printcomment( $1 ); fprintf(fd_out,"\n");
			  lexnextline();
			  cprintfclear();
			  PreDecrement = FALSE; }
;

/* INSTR: Regeln fuer Assembler-Instrunktionen einschliesslich
          Pseudo-Instruktionen (Direktiven) */
INSTR:	DIRECT 			{ $$ = cprintf("%s",$1); }
	| MNEMO 		{ $$ = cprintf("%s",$1); }
	| MNEMO	BLANK ADDR_LST	{ $$ = cprintf("%s\t%s",$1,$3); }
	| MNEMO BLANK ADDR ',' IMED_ADDRESS {
                                  $$ = cprintf("%s\t%s,%s",$1,$3,CalkRegisters($5.value)); }
	| MNEMO BLANK IMED_ADDRESS ',' ADDR {
				  $$ = cprintf("%s\t%s,%s",$1,CalkRegisters($3.value),$5); }
;

/* DIRECT: Regeln fuer die Direktiven */
DIRECT: DIR                     { $$ = cprintf("%s",$1); }
        | DIR BLANK EXP_LST     { $$ = cprintf("%s\t%s",$1,$3); }
	| DIR_FILL BLANK EXP ',' NUM EXP_RST {
	if ($5.value<1) 
           { yyerror("parse error");
           $$ = cprintf(";ERROR while converting fill-expression, size %d not allowed.\nERROR -> .fill\t%s,%s%s",(int)$5.value,$3,$5.string,$6); }
	else if ($5.value<=1) $$ = cprintf("dcb.b\t%s%s",$3,$6);
	else if ($5.value<=2) $$ = cprintf("dcb.w\t%s%s",$3,$6);
	else if ($5.value<=4) $$ = cprintf("dcb.l\t%s%s",$3,$6);
	else 
           { yyerror("parse error");
           $$ = cprintf(";ERROR while converting fill-expression, size %d not allowed.\nERROR -> .fill\t%s,%s%s",(int)$5.value,$3,$5.string,$6); }
	}
	| DIR_ALIGN BLANK EXP_LST {
			$$ = cprintf("%s%s",$1,$3 ); }
	| DIR_SET BLANK IDENT ',' EXP_LST {
			$$ = cprintf("\n%s\t%s\t%s",$3,$1,$5); }
;

/* EXP_RST: Regel fuer eine Liste von Null oder mehr Ausdruecken, beginnend
            mit einem Komma. */
EXP_RST:
	/* empty */		{ $$ = ""; }
	| ',' EXP_LST		{ $$ = cprintf(",%s",$2); }
;

/* EXP_LST: Regel fuer eine Liste von (mindestens einem) Aus-
            druecken (Expressions) getrennt durch Kommas */
EXP_LST:
	EXP_LST2		{ $$ = cprintf_worte( $1 ); }
;

/* EXP_LST2: Regel zum auswerten von Ausdruckslisten anhand von
	     Wortlisten. Hierrauf setzt EXP_LST auf, um aus
	     der Wortliste einen String zu machen */
EXP_LST2:
        EXP2                    { $$ = KlammerAussenWeg( $1 ); }
	| EXP2 ',' EXP_LST      { $$ = contact_wort(KlammerAussenWeg($1),
				       contact_wort(create_wort(","),
						    create_wort($3)
						   )); }
;

/* ADDR_LST: Regel fuer Source und Destination bei Assembler
             Befehlen */
ADDR_LST:
	ADDR			{ $$ = $1; }
	| ADDR ',' ADDR_LST	{ $$ = cprintf("%s,%s",$1,$3); }
;

/* ADDR: Regel zum erkennen der verschiedenen Adressierungsarten. */
ADDR:	ADDR ':' ADDR	{ $$ = cprintf("%s:%s",$1,$3); }
	| REG		{ $$ = $1; }
	| EXP		{ $$ = $1; }
	| EXP SIZE	{ $$ = cprintf("(%s).%s",$1,$2); }
	| '#' EXP	{ $$ = cprintf("#%s", $2); }
	| REG '@'	{ $$ = cprintf("(%s)",$1); }
/* Predecrement, Postincrement */
	| REG '@' '+'	{ $$ = cprintf("(%s)+",$1); }
	| REG '@' '-'	{ PreDecrement=TRUE; $$ = cprintf("-(%s)",$1); }
/* Displacement */
	| REG '@' '(' EXP ')' 	
			{ if (Modus&DRAUSSEN)
			    $$ = cprintf("%s(%s)",$4,$1);
			  else
			    $$ = cprintf("(%s,%s)",$4,$1); }
/* Word/Long Index */
	| REG '@' '(' REG SIZE ')'
		{ $$ = cprintf("(%s,%s.%s)",$1,$4,$5); }
	| REG '@' '(' EXP ',' REG SIZE ')'
		{ if (Modus&DRAUSSEN)
		    $$ = cprintf("%s(%s,%s.%s)",$4,$1,$6,$7);
		  else
		    $$ = cprintf("(%s,%s,%s.%s)",$4,$1,$6,$7); }
	| REG '@' '(' EXP SIZE ',' REG SIZE ')'
		{ if (Modus&DRAUSSEN)
		    $$ = cprintf("(%s)(%s,%s.%s)",$4,$1,$7,$8);
		  else
		    $$ = cprintf("((%s),%s,%s.%s)",$4,$1,$7,$8); }
/* Memory Pre-Indexed (68020) ohne Outer Displacement */
        | REG '@' '(' REG SIZE ':' EXP ')'
                { Setmc68020();
		  $$ = cprintf("(%s,%s.%s*%s)",$1,$4,$5,$7); }
	| REG '@' '(' EXP ',' REG SIZE ':' EXP ')'
		{ Setmc68020();
		  if (Modus&DRAUSSEN)
		    $$ = cprintf("%s(%s,%s.%s*%s)",$4,$1,$6,$7,$9);
		  else
		    $$ = cprintf("(%s,%s,%s.%s*%s)",$4,$1,$6,$7,$9); }
	| REG '@' '(' EXP SIZE ',' REG SIZE ':' EXP ')'
                { Setmc68020();
		  if (Modus&DRAUSSEN)
		    $$ = cprintf("(%s)(%s,%s.%s*%s)",$4,$1,$7,$8,$10);
		  else
                    $$ = cprintf("((%s),%s,%s.%s*%s)",$4,$1,$7,$8,$10); }
/* Memory Pre-Indexed (68020) mit Outer Displacement */
        | REG '@' '(' REG SIZE ':' EXP ')' AFFE '(' EXP ')'
		{ Setmc68020();
		  if (Modus&DRAUSSEN)
		    $$ = cprintf("%s([%s,%s.%s*%s])",$11,$1,$4,$5,$7);
		  else
		    $$ = cprintf("([%s,%s.%s*%s],%s)",$1,$4,$5,$7,$11); }
        | REG '@' '(' EXP ',' REG SIZE ':' EXP ')' AFFE '(' EXP ')'
		{ Setmc68020();
		  if (Modus&DRAUSSEN)
		    $$ = cprintf("%s(%s[%s,%s.%s*%s])",$13,$4,$1,$6,$7,$9); 
		  else
                    $$ = cprintf("([%s,%s,%s.%s*%s],%s)",$4,$1,$6,$7,$9,$13); }
/* Memory Post-Indexed (68020) */
	| REG '@' '(' EXP ')' AFFE '(' REG SIZE ':' EXP ',' EXP ')'
		{ Setmc68020();
		  if (Modus&DRAUSSEN)
		    $$ = cprintf("%s(%s[%s],%s.%s*%s)",$13,$4,$1,$8,$9,$11);
		  else
		    $$ = cprintf("([%s,%s],%s.%s*%s,%s)",$4,$1,$8,$9,$11,$13); }
/* Bitfeld Addressierungsart */
        | ADDR '{' '#' EXP ':' '#' EXP '}'
                { Setmc68020();
                  $$ = cprintf("%s{%s:%s}",$1,$4,$7); }
;

/* AFFE: Regel dient bei den Adressierungsarten fuer einen Optionalen '@' */
AFFE:	/* empty */
	| '@'
;

/* EXP: Regel dient zum erkennen eines Rechenausdruckes. Es wird sogar
        in der Reihenfolge der Prioritaeten der Operationen ausgewertet.
        Die Regel besteht aus den Regeln EXP und EXP2, um ein Wegstreichen
        der aeussersten Klammer zu realisieren. */
EXP:	EXP2			{ $$ =
				  cprintf_worte(KlammerAussenWeg($1)); }
;

EXP2:	NUM			{ $$ = create_wort($1.string); }
	| IDENT			{ $$ = create_wort($1); }
	| EXP2 '+' EXP2		{ $$ = 
				  contact_wort( create_wort("("),
				  contact_wort( $1,
				  contact_wort( create_wort("+"),
				  contact_wort( $3,
				                create_wort(")") ))));
				  }
        | EXP2 '-' EXP2   	{ $$ =
                                  contact_wort( create_wort("("),
				  contact_wort( $1,
				  contact_wort( create_wort("-"),
				  contact_wort( $3,
	      				        create_wort(")") ))));
				  }
        | EXP2 '*' EXP2   	{ $$ =
			          contact_wort( create_wort("("),
				  contact_wort( $1,
				  contact_wort( create_wort("*"),
				  contact_wort( $3,
		      		                create_wort(")") ))));
				  }
        | EXP2 '/' EXP2   	{ $$ =
				  contact_wort( create_wort("("),
				  contact_wort( $1,
				  contact_wort( create_wort("/"),
				  contact_wort( $3,
						create_wort(")") ))));
				  }
        | EXP2 '%' EXP2   	{ $$ =
				  contact_wort( create_wort("("),
				  contact_wort( $1,
				  contact_wort( create_wort("%"),
				  contact_wort( $3,
						create_wort(")") ))));
				  }
        | '-' EXP2 %prec NEG	{ $$ = 
				  contact_wort( create_wort("(-"),
				  contact_wort( $2,
						create_wort(")") ));
				  }
        | EXP2 '&' EXP2   	{ $$ = 
				  contact_wort( create_wort("("),
				  contact_wort( $1,
				  contact_wort( create_wort("&"),
				  contact_wort( $3,
						create_wort(")") ))));
				  }
        | EXP2 '|' EXP2   	{ $$ =
				  contact_wort( create_wort("("),
				  contact_wort( $1,
				  contact_wort( create_wort("|"),
				  contact_wort( $3,
						create_wort(")") ))));
				  }
        | EXP2 '^' EXP2   	{ $$ =
				  contact_wort( create_wort("("),
				  contact_wort( $1,
				  contact_wort( create_wort("^"),
				  contact_wort( $3,
						create_wort(")") ))));
				  }
        | '~' EXP2   		{ $$ =
				  contact_wort( create_wort("(~"),
				  contact_wort( $2,
						create_wort(")") ));
				  }
        | EXP2 '>' EXP2   	{ $$ =
				  contact_wort( create_wort("("),
				  contact_wort( $1,
				  contact_wort( create_wort(">"),
				  contact_wort( $3,
						create_wort(")") ))));
				  }
        | EXP2 '>' '>' EXP2       { $$ =
				  contact_wort( create_wort("("),
				  contact_wort( $1,
				  contact_wort( create_wort(">"),
				  contact_wort( $4,
						create_wort(")") ))));
				  }
        | EXP2 '<' EXP2           { $$ =
				  contact_wort( create_wort("("),
				  contact_wort( $1,
				  contact_wort( create_wort("<"),
				  contact_wort( $3,
						create_wort(")") ))));
				  }
        | EXP2 '<' '<' EXP2       { $$ =
				  contact_wort( create_wort("("),
				  contact_wort( $1,
				  contact_wort( create_wort("<"),
				  contact_wort( $4,
						create_wort(")") ))));
				  }
	| '(' EXP2 ')'		  { $$ =
				  contact_wort( create_wort("("),
				  contact_wort( $2,
				                create_wort(")") ));
				  }
;
%%
static void printcomment( const char *s )
/* Gibt den String "s" als Kommentar aus.
   Jede neue Zeile muss mit einem ";" beginnen. */
{
if (s!=NULL)
  for ( ; *s!=0; s++)
    putc(*s,fd_out);
}

static short CalkRegistersHelp( long *pt_value, short *pt_n )
{
short rest;
if (PreDecrement==TRUE)
  {
  rest = (*pt_value) & 0x8000;
  *pt_value = (*pt_value) << 1;
  }
else
  {
  rest = (*pt_value) & 0x1;
  *pt_value = (*pt_value) >> 1;
  }
(*pt_n)++;
return rest;
}

static char *CalkRegisters( long value )
{
char *result;
char *register_list[16] =  { "d0","d1","d2","d3","d4","d5","d6","d7",
                             "a0","a1","a2","a3","a4","a5","a6","a7" };
short i,n;
result=NULL;
for (n=0; n<16; )
  {
  if (CalkRegistersHelp( &value, &n )!=0)
    {
    i=n;
    while (CalkRegistersHelp( &value, &n)!=0)
      ;
    if (result==NULL) result=cprintfappendinit();
    else cprintfappend("/");
    if (n-1==i)
      cprintfappend("%s",register_list[n-2]);
    else
      cprintfappend("%s-%s",register_list[i-1],register_list[n-2]);
    }
  }
if (result==NULL) result="0";
return result;
}

static struct t_worte *KlammerAussenWeg( struct t_worte *w )
{
int n;
if (w==NULL) return w;
for(n=0; w->letztes->wort[n]!=0; n++)
  ;
if (n==0) return w;
if (w->erstes->wort[0]=='(' && strcmp(w->letztes->wort,")")==0)
  {
  w->letztes->wort = "";
  w->erstes->wort++;
  }
return w;
}

struct t_worte *create_wort( char *kette )
/* Aus einer Zeichenkette wird eine einelementige Wortliste */
{
struct t_worte *wort;
struct t_wortkette *wortkette;

wort = (struct t_worte *) malloc( sizeof(*wort) );
if (wort==NULL)
  { fprintf(stderr,"Speicher voll.\n");
    exit(1); }
wortkette = (struct t_wortkette *) malloc( sizeof(*wortkette) );
if (wortkette==NULL)
  { fprintf(stderr,"Speicher voll.\n");
    exit(1); }
wortkette->wort=kette;
wortkette->naechstes=NULL;
wort->letztes=wort->erstes=wortkette;
return wort;
}

struct t_worte *contact_wort(struct t_worte *w1,struct t_worte *w2)
/* Zwei Wortlisten w1 und w2 werden aneinandergehaengt */
{
w1->letztes->naechstes=w2->erstes;
w1->letztes=w2->letztes;
free(w2);
return w1;
}

char *cprintf_worte( struct t_worte *w )
/* Aus einer Wortliste wird ein String gemacht. Der Speicher
   fuer den String wird durch cprintf() alloziert. Der Speicher
   fuer die Wortliste wird freigegeben */
{
char *result;
struct t_wortkette *wortkette, *naechstes;

result = cprintfappendinit();
for (wortkette=w->erstes; wortkette!=NULL; wortkette=naechstes)
  {
  cprintfappend("%s",wortkette->wort);
  naechstes=wortkette->naechstes;
  free(wortkette);
  }
free(w);
return result;
}

