/*  Core Wars.
 *  Copyright (C) 1999  Walter Hofmann
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 2 as 
 *  published by the Free Software Foundation.
 *
 *  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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

#include "main.h"
#include "program.h"
#include "program-cw.h"
#include "options.h"
  
  int current_line;
  struct cw_line *root_line;
  char cw_error_message[1024];
  
  void cw_error (char *s);
  int cw_lex (void);
  void cw_restart(FILE *input_file);
%}

%union {
  int			integer;
  struct cw_line	*line;
  struct cw_operand	*operand;
  char			*name;
}

%token <integer> INTEGER
%token <name> IDENTIFIER
%token WHITESPACE
%token COMMENT
%token <name> STRING
%token <integer> KEYWORD

%type <line> input line command instruction
%type <name> label
%type <operand> operand

%% /* Grammar rules and actions follow */

program:	{ current_line = 1; } input		{ root_line = $2; }

input:		/* empty */	{ $$ = NULL; }
		| line input	{ if ($1) { $$ = $1; $1->next = $2; } else { $$ = $2; } }
		;

line:		whitespace comment '\n'			{ $$ = NULL; current_line++; }
		| whitespace command comment '\n'	{ $$ = $2; $$->line_number = current_line++; }
 		;

command:	label instruction			{ $$ = $2; $$->label = $1; }
		;

label:		/* empty */				{ $$ = NULL; }
		| IDENTIFIER ':' whitespace		{ $$ = $1; }
		;

instruction:	KEYWORD WHITESPACE operand whitespace ',' whitespace operand whitespace	{
			$$ = malloc (sizeof (struct cw_line));
			$$->command = $1;
			$$->operand1 = $3;
			$$->operand2 = $7;
			}
		| KEYWORD WHITESPACE operand whitespace	{
			$$ = malloc (sizeof (struct cw_line));
			$$->command = $1;
			$$->operand1 = $3;
			$$->operand2 = NULL;
			}
		;

operand:	STRING				{ 
			$$ = malloc (sizeof (struct cw_operand));
			$$->accessmode = AM_STRING;
			$$->operand.name = $1;
			}
		| INTEGER				{
			$$ = malloc (sizeof (struct cw_operand));
			$$->accessmode = AM_CONSTANT;
			$$->operand.value = $1;
			}
		| '&' whitespace IDENTIFIER	{
			$$ = malloc (sizeof (struct cw_operand));
			$$->accessmode = AM_ADDRESS;
			$$->operand.name = $3;
			}
		| IDENTIFIER			{
			$$ = malloc (sizeof (struct cw_operand));
			$$->accessmode = AM_VALUE;
			$$->operand.name = $1;
			}
		| '[' IDENTIFIER ']'		{
			$$ = malloc (sizeof (struct cw_operand));
			$$->accessmode = AM_INDIRECT;
			$$->operand.name = $2;
			}
		;

comment:	/* empty */	{}
		| COMMENT	{}
		;

whitespace:	/* empty */	{}
		| WHITESPACE	{}
		;

%%

void free_parsed_program (struct cw_line* l)
{
  struct cw_line *next;
  while (l)
    {
      next = l->next;
      if (l->operand1)
	{
	  if (l->operand1->accessmode!=AM_CONSTANT)
	    free (l->operand1->operand.name);
	  free (l->operand1);
	}
      if (l->operand2)
	{
	  if (l->operand2->accessmode!=AM_CONSTANT)
	    free (l->operand2->operand.name);
	  free (l->operand2);
	}
      free (l);
      l = next;
    }
}

void cw_dump_program (struct program *pr)
{
  printf ("Not implemented.\n");
  exit (1);
}

void cw_dump_program_internal (struct program *pr)
{
  printf ("Not implemented.\n");
  exit (1);
}

/* Loads error and code into a struct program. Unloads old error and 
   old code if neccessary. Only fails on fatal errors. */
int cw_load_program (struct program *p)
{
  struct label {
    char *name;
    int address;
  } ;

  struct cw_line *l, *next;
  char error_message[1024];
  int opcode_number, label_number;
  struct label *label_table;
  int i, n;
  FILE *f;

  unload_program (p);
  f = fopen (p->filename, "r");
  if (!f)
    return FAIL;
  cw_restart (f);
  if (cw_parse()==0)
    {
      fclose (f);
      l = root_line;
      label_number = 0;
      while (l)
	{
	  if (l->command==CMD_TITLE)
	    if (p->title)
	      {
		sprintf (error_message, "%s (%d): Second 'title' keyword", 
			 p->filename, l->line_number);
		p->error = strdup (error_message);
		free_parsed_program (root_line);
		return OK;
	      }
	    else if (l->operand1->accessmode!=AM_STRING)
	      {
		sprintf (error_message, "%s (%d): 'title' has string argument", 
			 p->filename, l->line_number);
		p->error = strdup (error_message);
		free_parsed_program (root_line);
		return OK;
	      }
	    else if (l->operand2)
	      {
		sprintf (error_message, "%s (%d): 'title' has one argument", 
			 p->filename, l->line_number);
		p->error = strdup (error_message);
		free_parsed_program (root_line);
		return OK;
	      }
	    else if (l->label)
	      {
		sprintf (error_message, "%s (%d): 'title' cannot have a label", 
			 p->filename, l->line_number);
		p->error = strdup (error_message);
		free_parsed_program (root_line);
		return OK;
	      }
	    else
	      p->title = strdup (l->operand1->operand.name);		
	  else
	  if (l->command==CMD_AUTHOR)
	    if (p->author)
	      {
		sprintf (error_message, "%s (%d): Second 'author' keyword", 
			 p->filename, l->line_number);
		p->error = strdup (error_message);
		free_parsed_program (root_line);
		return OK;
	      }
	    else if (l->operand1->accessmode!=AM_STRING)
	      {
		sprintf (error_message, "%s (%d): 'author' has string argument", 
			 p->filename, l->line_number);
		p->error = strdup (error_message);
		free_parsed_program (root_line);
		return OK;
	      }
	    else if (l->operand2)
	      {
		sprintf (error_message, "%s (%d): 'author' has one argument", 
			 p->filename, l->line_number);
		p->error = strdup (error_message);
		free_parsed_program (root_line);
		return OK;
	      }
	    else if (l->label)
	      {
		sprintf (error_message, "%s (%d): 'author' cannot have a label", 
			 p->filename, l->line_number);
		p->error = strdup (error_message);
		free_parsed_program (root_line);
		return OK;
	      }
	    else
	      p->author = strdup (l->operand1->operand.name);		
	  else
	    {
	      if (l->label)
		label_number++;
	      switch (l->command) 
		{
		case CMD_DATA: 
		case CMD_JUMP: 
		case CMD_FORK: 
		case CMD_OWN: 
		  if (l->operand2)
		    {
		      sprintf (error_message, "%s (%d): One operand expected", 
			       p->filename, l->line_number);
		      p->error = strdup (error_message);
		      free_parsed_program (root_line);
		      return OK;
		    }
		  break;
		case CMD_MOVE: 
		case CMD_ADD: 
		case CMD_NEG: 
		case CMD_AND: 
		case CMD_NOT: 
		case CMD_MUL: 
		case CMD_DIV: 
		case CMD_MOD: 
		case CMD_EQUAL: 
		case CMD_LESS: 
		case CMD_INFO: 
		case CMD_SYSTEM:
		case CMD_LOOP:
		case CMD_MOVEI:
		  if (!l->operand2)
		    {
		      sprintf (error_message, "%s (%d): Two operands expected", 
			       p->filename, l->line_number);
		      p->error = strdup (error_message);
		      free_parsed_program (root_line);
		      return OK;
		    }
		  break;
		}
	      switch (l->command) 
		{
		case CMD_DATA:
		  if (l->operand1->accessmode!=AM_CONSTANT && l->operand1->accessmode!=AM_ADDRESS)
		    {
		      sprintf (error_message, "%s (%d): 'data' expects constant or address", 
			       p->filename, l->line_number);
		      p->error = strdup (error_message);
		      free_parsed_program (root_line);
		      return OK;
		    }
		  break;
		case CMD_MOVE: 
		case CMD_ADD: 
		case CMD_NEG: 
 		case CMD_AND: 
		case CMD_NOT:
 		case CMD_MUL: 
 		case CMD_DIV: 
 		case CMD_MOD: 
		  if (l->operand2->accessmode!=AM_VALUE && l->operand2->accessmode!=AM_INDIRECT)
		    {
		      sprintf (error_message, "%s (%d): 2nd operand must be variable or indirect", 
			       p->filename, l->line_number);
		      p->error = strdup (error_message);
		      free_parsed_program (root_line);
		      return OK;
		    }
		  break;
		case CMD_JUMP: 
		case CMD_FORK: 
		case CMD_OWN:
		  if (l->operand1->accessmode!=AM_VALUE && l->operand1->accessmode!=AM_INDIRECT)
		    {
		      sprintf (error_message, "%s (%d): operand must be a label or indirect", 
			       p->filename, l->line_number);
		      p->error = strdup (error_message);
		      free_parsed_program (root_line);
		      return OK;
		    }
		  break;
		case CMD_INFO:
		  if (l->operand1->accessmode!=AM_CONSTANT)
		    {
		      sprintf (error_message, "%s (%d): 1st operand must be constant", 
			       p->filename, l->line_number);
		      p->error = strdup (error_message);
		      free_parsed_program (root_line);
		      return OK;
		    }
		  if (l->operand2->accessmode!=AM_VALUE && l->operand2->accessmode!=AM_INDIRECT)
		    {
		      sprintf (error_message, "%s (%d): 2nd operand must be variable or indirect", 
			       p->filename, l->line_number);
		      p->error = strdup (error_message);
		      free_parsed_program (root_line);
		      return OK;
		    }
		  break;
		case CMD_SYSTEM:
		  if (l->operand2->accessmode!=AM_CONSTANT)
		    {
		      sprintf (error_message, "%s (%d): 2nd operand must be constant", 
			       p->filename, l->line_number);
		      p->error = strdup (error_message);
		      free_parsed_program (root_line);
		      return OK;
		    }
		  break;
		case CMD_LOOP:
		  if (l->operand1->accessmode!=AM_VALUE && l->operand1->accessmode!=AM_INDIRECT)
		    {
		      sprintf (error_message, "%s (%d): 1st operand must be variable or indirect", 
			       p->filename, l->line_number);
		      p->error = strdup (error_message);
		      free_parsed_program (root_line);
		      return OK;
		    }
		  if (l->operand2->accessmode!=AM_VALUE && l->operand2->accessmode!=AM_INDIRECT)
		    {
		      sprintf (error_message, "%s (%d): 2nd operand must be a label or indirect", 
			       p->filename, l->line_number);
		      p->error = strdup (error_message);
		      free_parsed_program (root_line);
		      return OK;
		    }
		  break;
		case CMD_MOVEI:
		  if (l->operand1->accessmode!=AM_VALUE)
		    {
		      sprintf (error_message, "%s (%d): 1st operand must be variable", 
			       p->filename, l->line_number);
		      p->error = strdup (error_message);
		      free_parsed_program (root_line);
		      return OK;
		    }
		  if (l->operand2->accessmode!=AM_VALUE)
		    {
		      sprintf (error_message, "%s (%d): 2nd operand must be variable", 
			       p->filename, l->line_number);
		      p->error = strdup (error_message);
		      free_parsed_program (root_line);
		      return OK;
		    }
		  break;
		}
	    }
	  l = l->next;
	}
      if (!p->title)
	p->title = strdup (p->filename);
      if (!p->author)
	p->author = strdup ("unknown author");
      label_table = malloc (sizeof (struct label)*label_number+1);
      l = root_line;
      opcode_number = 0;
      n = 0;
      while (l)
	{
	  if (l->label)
	    {
	      for (i=0; i<n; i++)
		if (strcmp (l->label, label_table[i].name)==0)
		  {
		    snprintf (error_message, 1024, "%s (%d): Duplicate label (%s)", 
			      p->filename, l->line_number, l->label);
		    error_message[1023] = '\0';
		    p->error = strdup (error_message);
		    free (label_table);
		    free_parsed_program (root_line);
		    return OK;
		  }
	      label_table[n].name = l->label;
	      label_table[n++].address = opcode_number;
	    }
	  if (l->command!=CMD_TITLE && l->command!=CMD_AUTHOR)
	    opcode_number++;
	  l = l->next;
	}
      if (!opcode_number)
	{
	  sprintf (error_message, "%s: Program contains no code", p->filename);
	  p->error = strdup (error_message);
	  free (label_table);
	  free_parsed_program (root_line);
	  return OK;
	}
      l = root_line;
      while (l)
	{
	  switch (l->operand1->accessmode)
	    {
	    case AM_ADDRESS:
	    case AM_VALUE:
	    case AM_INDIRECT:
	      for (i=0; i<label_number; i++)
		if (strcmp (l->operand1->operand.name, label_table[i].name)==0)
		  break;
	      if (i==label_number)
		{
		  snprintf (error_message, 1024, "%s (%d): Undefined label (%s)", 
			   p->filename, l->line_number, l->operand1->operand.name);
		  error_message[1023] = '\0';
		  p->error = strdup (error_message);
		  free (label_table);
		  free_parsed_program (root_line);
		  return OK;
		}
	    }
	  if (l->operand2)
	    switch (l->operand2->accessmode)
	      {
	      case AM_ADDRESS:
	      case AM_VALUE:
	      case AM_INDIRECT:
		for (i=0; i<label_number; i++)
		  if (strcmp (l->operand2->operand.name, label_table[i].name)==0)
		    break;
		if (i==label_number)
		  {
		    snprintf (error_message, 1024, "%s (%d): Undefined label (%s)", 
			     p->filename, l->line_number, l->operand2->operand.name);
		    error_message[1023] = '\0';
		    p->error = strdup (error_message);
		    free (label_table);
		    free_parsed_program (root_line);
		    return OK;
		  }
	      }
	  l = l->next;
	}
      if (opcode_number>MAX_LENGTH)
	{
	  snprintf (error_message, 1024, "%s: Program exceeds maximum length (by %d instructions)", 
		    p->filename, opcode_number-MAX_LENGTH);
	  error_message[1023] = '\0';
	  p->error = strdup (error_message);
	  free (label_table);
	  free_parsed_program (root_line);
	  return OK;
	}
      p->lang.cw.opcode_number = opcode_number;
      p->lang.cw.code = malloc (sizeof (struct line *)*opcode_number);
      opcode_number = 0;
      l = root_line;
      while (l)
	{
	  next = l->next;
	  if (l->command==CMD_TITLE || l->command==CMD_AUTHOR)
	    {
	      free (l->operand1->operand.name);
	      free (l->operand1);
	      free (l);
	    }
	  else
	    {
	      switch (l->operand1->accessmode)
		{
		case AM_ADDRESS:
		case AM_VALUE:
		case AM_INDIRECT:
		  for (i=0; i<label_number; i++)
		    if (strcmp (l->operand1->operand.name, label_table[i].name)==0)
		      {
			free (l->operand1->operand.name);
			l->operand1->operand.value = label_table[i].address-opcode_number;
			break;
		      }
		}
	      if (l->operand2)
		switch (l->operand2->accessmode)
		  {
		  case AM_ADDRESS:
		  case AM_VALUE:
		  case AM_INDIRECT:
		    for (i=0; i<label_number; i++)
		      if (strcmp (l->operand2->operand.name, label_table[i].name)==0)
			{
			  free (l->operand2->operand.name);
			  l->operand2->operand.value = label_table[i].address-opcode_number;
			  break;
			}
		  }
	      l->next = NULL;
	      l->label = NULL;
	      p->lang.cw.code[opcode_number] = l;
	      opcode_number++;
	    }
	  l = next;
	}
      for (i=0; i<label_number; i++)
	free (label_table[i].name);
      free (label_table);
    }
  else
    {
      fclose (f);
      sprintf (error_message, "%s (%d): Syntax error", p->filename, current_line);
      p->error = strdup (error_message);
    }
  return OK;
}

/* Unloads error and code from struct program. */
void cw_free_program (struct program *p)
{
  if (p->lang.cw.code)
    free (p->lang.cw.code);
  p->lang.cw.code = NULL;
}

void cw_error (char *s)
{
}
