%option noyywrap
%x incl
%x define
%x definition
%x title
%x subtitle

%{
/* lexical analyser for gpasm
   Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005
   James Bowman, Craig Franklin

This file is part of gputils.

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

gputils 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 gputils; see the file COPYING.  If not, write to
the Free Software Foundation, 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA.  */

#include "stdhdr.h"

#include "libgputils.h"
#include "gpasm.h"
#include "parse.h"
#include "scan.h"
#include "deps.h"
#include "gperror.h"
#include "directive.h"
#include "evaluate.h"
#include "macro.h"
#include "coff.h"

#define OPERATOR(x)  return (yylval.i = (x))
/* YY_UNPUT not used, suppress the warning */
#define YY_NO_UNPUT

enum identtype { defines, directives, globals, macros, opcodes, unknown_type };
enum identtype identify(char *);

static void push_string(char *str);
static int found_end();
static int found_eof();

static char *check_defines(char *symbol);

static struct symbol *current_definition;	/* Used in #define */
static int quoted; /* Used to prevent #define expansion in ifdef and 
                      ifndef... */
int force_decimal; /* Used to force radix to decimal for some directives */
int force_ident; /* Used to force numbers to identifiers for processor names */

%}

IDENT  [.]?[a-z_\x80-\xff?@#][a-z_0-9\x80-\xff.?@#]*
ESCCH  \\([abfnrtv\\?'"]|0[0-7]{2}|x[0-9a-f]{2})
STR_QCHAR  ([^"\n]|{ESCCH})
NUMCHAR [0-9a-z]

%%
^[ \t]*#?include[ \t]*[<"]? { BEGIN(incl); }
<incl>[^<>";\r\n]*[>"]?	 { /* got the include file name */
			   char *pc = &yytext[yyleng - 1];
			   if ((*pc == '"') || (*pc == '>'))
			     *pc = '\0';
			   BEGIN(INITIAL);
			   yylval.s = strdup(yytext);
			   return INCLUDE;
			 }
<<EOF>>		 	 {
			   if (found_eof())
			     yyterminate();
			 }
end		         {
			   found_end();
			   yyterminate();
			 }
^[ \t]*title[ \t]*[<"]?  { BEGIN(title); }
<title>[^<"\r\n]*[>"]?   { /* got the title text */
#define LEN sizeof(state.lst.title_name)
                           yytext[yyleng - 1] = '\0';
                           strncpy(state.lst.title_name, yytext, LEN);
                           BEGIN(INITIAL);
#undef LEN
                         }
^[ \t]*subtitle[ \t]*[<"]?  { BEGIN(subtitle); }
^[ \t]*subtitl[ \t]*[<"]?   { BEGIN(subtitle); }
^[ \t]*stitle[ \t]*[<"]?    { BEGIN(subtitle); }
<subtitle>[^<"\r\n]*[>"]?   { /* got the subtitle text */
#define LEN sizeof(state.lst.subtitle_name)
                           yytext[yyleng - 1] = '\0';
                           strncpy(state.lst.subtitle_name, yytext, LEN);
                           BEGIN(INITIAL);
#undef LEN
                         }
cblock			 {
			   return CBLOCK;
			 }
errorlevel		 {
			   yylval.s = strdup(yytext);
			   return ERRORLEVEL;
			 }
endc			 {
			   return ENDC;
			 }
fill[ \t]*\(     	 {
			   /* fill with ( ) as first argument */
			   yylval.i = FILL;
			   return FILL;
			 }
^[ \t]*#define[ \t]+ 	 {
                           BEGIN(define);
			 }
<define>{IDENT}		 {
                           if((asm_enabled()) && (!state.mac_prev)) {
			     if ((get_symbol(state.stDefines, yytext) != NULL)
                                  && (state.pass == 1)) {
			       /* FIXME: find a more elegant way to do this */
			       state.pass = 2;
			       gperror(GPE_DUPLAB, NULL);
			       exit(1);
			     }
			     current_definition = add_symbol(state.stDefines, 
                                                             yytext);
                           }
			   BEGIN(definition);
			 }
<definition>.*$	 	 {
                           if((asm_enabled()) && (!state.mac_prev)) {
			     char *string_ptr = yytext;

			     /* Should have a #define in progress */
			     assert(current_definition != NULL);

			     /* if there is a comment remove it */
			     while(*string_ptr != '\0') {
                               if (*string_ptr == ';') {
                                 *string_ptr = '\0';
                                 break;
                               }                                 
                               string_ptr++;
			     }

			     /* remove leading spaces or tabs */
			     string_ptr = yytext;
			     while(*string_ptr == '\t' || *string_ptr == ' ') {
			       string_ptr++;
			     }

                             annotate_symbol(current_definition, 
                                             strdup(string_ptr));
			     current_definition = NULL;
                           }
			   BEGIN(INITIAL);
                         }
upper			 {
			   yylval.i = UPPER;
			   return UPPER;
			 }
high			 {
			   yylval.i = HIGH;
			   return HIGH;
			 }
low		     	 {
			   yylval.i = LOW;
			   return LOW;
			 }
list		     	 {
			   yylval.s = strdup(yytext);
			   return LIST;
			 }
processor	     	 {
			   yylval.s = strdup(yytext);
			   return PROCESSOR;
			 }
#?if			 {
			   /* #if and if can appear in column 1 */
			   yylval.s = strdup(yytext);
			   return IDENTIFIER;
			 }
#?else   	 	 {
			   /* #else and else can appear in column 1 */
			   yylval.s = strdup(yytext);
			   return IDENTIFIER;
			 }
#?endif  	 	 {
			   /* #endif and endif can appear in column 1 */
			   yylval.s = strdup(yytext);
			   return IDENTIFIER;
			 }
#?ifdef			 {
			   /* #ifdef and ifdef can appear in column 1 */
			   quoted = 1;
			   yylval.s = strdup(yytext);
			   return IDENTIFIER;
			 }
#?ifndef		 {
			   /* #ifndef and ifndef can appear in column 1 */
			   quoted = 1;
			   yylval.s = strdup(yytext);
			   return IDENTIFIER;
			 }
#undefine 	 	 {
			   /* #undefine can appear in column 1 */
			   quoted = 1;
			   yylval.s = strdup(yytext);
			   return IDENTIFIER;
			 }
"."line			 {
			   yylval.s = strdup(yytext);
			   return DEBUG_LINE;
			 }
^{IDENT}#v\(             {
			   char *symbol;

                           yytext[strlen(yytext) - 3] = '\0';

			   symbol = check_defines(yytext);
			   if (symbol) {
                             yylval.s = strdup(symbol);
			   } else {
                             yylval.s = strdup(yytext);
			   }
                           return VARLAB_BEGIN;
                         }
^{IDENT}:?		 { 
			   int has_collon = 0;
			   struct symbol *sym;
			   struct macro_head *h;
			   char *subst;
                           
			   if (yytext[strlen(yytext) - 1] == ':') {
			     yytext[strlen(yytext) - 1] = '\0';
			     has_collon = 1;
			   }
			   yylval.s = strdup(yytext);
                           switch(identify(yytext)) {
                             case defines:
                               sym = get_symbol(state.stTopDefines, yytext);
                               subst = get_symbol_annotation(sym);
                               push_string(subst);
                               break;
                             case directives:
                               gpwarning(GPW_DIR_COLUMN_ONE, NULL);
                               if (has_collon)
                                 gperror(GPE_BADCHAR, "Illegal character (:)");
                               return IDENTIFIER;
                               break;
                             case macros:
                               if(asm_enabled()) {
			         /* make sure macro definition on second pass
                                    is ignored */
			         sym = get_symbol(state.stMacros, yytext);
			         h = get_symbol_annotation(sym);
                                 if (h->line_number == state.src->line_number) {
			           return LABEL;
			         } else {
			           gpwarning(GPW_MACRO_COLUMN_ONE, NULL);
			           if (has_collon)
                                    gperror(GPE_BADCHAR, 
                                             "Illegal character (:)");
			           return IDENTIFIER;
			         }
                               } else {
                                 /* if assembly is not enabled don't issue 
                                 warnings about macro calls in column 1, they 
                                 could be an alternate definition */ 
                                 return LABEL;			   
                               }
                               break;
                             case opcodes:
                               gpwarning(GPW_OP_COLUMN_ONE, NULL);
                               if (has_collon)
                                 gperror(GPE_BADCHAR, "Illegal character (:)");
                               return IDENTIFIER;
                               break;
                             case unknown_type:
                               return LABEL;
                             default:
                               return LABEL;
                           }			   
			 }
{IDENT}#v\(              {
			   char *symbol;

                           yytext[strlen(yytext) - 3] = '\0';

			   symbol = check_defines(yytext);
			   if (symbol) {
                             yylval.s = strdup(symbol);
			   } else {
                             yylval.s = strdup(yytext);
			   }
                           return VAR_BEGIN;
                         }
\)[a-z_0-9\x80-\xff?.]+  {
			   char *symbol;

			   symbol = check_defines(yytext+1);
			   if (symbol) {
                             yylval.s = strdup(symbol);
			   } else {
                             yylval.s = strdup(yytext+1);
			   }
                           return VAR_END;
                         }
{IDENT}			 {
			   char *symbol;

			   symbol = check_defines(yytext);
			   if (symbol) {
 			     char buffer[BUFSIZ];
                               
                             /* Make the substitution with a leading space,
                                so it won't be a label */
                             sprintf(buffer, " %s", symbol); 
                             push_string(buffer);
                           } else {
                             yylval.s = strdup(yytext);
			     return IDENTIFIER;
                           }
			 }
0x{NUMCHAR}+             {
                           yylval.i = stringtolong(yytext + 2, 16);
			   return NUMBER;
			 }
{NUMCHAR}+b              {
                           if (force_ident) {
                             yylval.s = strdup(yytext);
                             return IDENTIFIER;
                           } else if (state.radix == 16) {
                             yylval.i = stringtolong(yytext, 16);                           
  			     return NUMBER;
			   } else {
			     yytext[yyleng - 1] = '\0';
                             yylval.i = stringtolong(yytext, 2);
  			     return NUMBER;
			   }
			 }
b'-?{NUMCHAR}+'          {
                           yytext[yyleng - 1] = '\0';
			   yylval.i = stringtolong(yytext + 2, 2);
			   return NUMBER;
			 }
{NUMCHAR}+[oq]           {			 
                           if (force_ident) {
                             yylval.s = strdup(yytext);
                             return IDENTIFIER;
                           } else {
                             yytext[yyleng - 1] = '\0';
                             yylval.i = stringtolong(yytext, 8);
			     return NUMBER;
			   }
			 }
[oq]'-?{NUMCHAR}+'       {
                           yytext[yyleng - 1] = '\0';
			   yylval.i = stringtolong(yytext + 2, 8);
			   return NUMBER;
			 }
{NUMCHAR}+d              {
                           if (force_ident) {
                             yylval.s = strdup(yytext);
                             return IDENTIFIER;
                           } else if (state.radix == 16) {
                             yylval.i = stringtolong(yytext, 16);                           
  			     return NUMBER;
			   } else {
			     yytext[yyleng - 1] = '\0';
                             yylval.i = stringtolong(yytext, 10);
  			     return NUMBER;
			   }
			 }
d'-?[0-9]+'		 {
                           yytext[yyleng - 1] = '\0';
			   yylval.i = stringtolong(yytext + 2, 10);
			   return NUMBER;
			 }			
"."[0-9]+	         {
                           yylval.i = stringtolong(yytext + 1, 10);
			   return NUMBER;
			 }
{NUMCHAR}+h              {			 
                           if (force_ident) {
                             yylval.s = strdup(yytext);
                             return IDENTIFIER;
                           } else {
                             yytext[yyleng - 1] = '\0';
                             yylval.i = stringtolong(yytext, 16);
			     return NUMBER;
			   }
			 }			 
h'-?{NUMCHAR}+'          {
                           yytext[yyleng - 1] = '\0';
			   yylval.i = stringtolong(yytext + 2, 16);
			   return NUMBER;
			 }
{NUMCHAR}+		 {
                           if (force_ident) {
                             yylval.s = strdup(yytext);
                             return IDENTIFIER;
                           } else if (force_decimal) {
                             yylval.i = stringtolong(yytext, 10);
   			     return NUMBER;
                           } else {
                             yylval.i = stringtolong(yytext, state.radix);
   			     return NUMBER;
                           }
			 }
\"{STR_QCHAR}\"          {
                           yylval.i = gpasm_magic(yytext + 1);
			   return NUMBER;
                         }
\"{STR_QCHAR}*\"?        {
			   char *pc = &yytext[yyleng - 1];
			   if (*pc == '"')
			     *pc = '\0';
                           else
			     gpwarning(GPW_MISSING_QUOTE, NULL);                           
                           yylval.s = strdup(yytext + 1);
			   return STRING;
                         }
'{STR_QCHAR}'            {
                           yylval.i = gpasm_magic(yytext + 1);
			   return NUMBER;
                         }
A'{STR_QCHAR}'           {
                           yylval.i = yytext[2];
			   return NUMBER;
                         }
"<<"			 OPERATOR(LSH);
">>"			 OPERATOR(RSH);
">="			 OPERATOR(GREATER_EQUAL);
"<="			 OPERATOR(LESS_EQUAL);
"=="			 OPERATOR(EQUAL);
"!="			 OPERATOR(NOT_EQUAL);
"&&"			 OPERATOR(LOGICAL_AND);
"||"			 OPERATOR(LOGICAL_OR);

"+="			 OPERATOR(ASSIGN_PLUS);
"-="			 OPERATOR(ASSIGN_MINUS);
"*="			 OPERATOR(ASSIGN_MULTIPLY);
"/="			 OPERATOR(ASSIGN_DIVIDE);
"%="			 OPERATOR(ASSIGN_MODULUS);
"<<="			 OPERATOR(ASSIGN_LSH);
">>="			 OPERATOR(ASSIGN_RSH);
"&="			 OPERATOR(ASSIGN_AND);
"|="			 OPERATOR(ASSIGN_OR);
"^="			 OPERATOR(ASSIGN_XOR);

"++"			 OPERATOR(INCREMENT);
"--"			 OPERATOR(DECREMENT);

"*+"			 OPERATOR(TBL_POST_INC);
"*-"			 OPERATOR(TBL_POST_DEC);
"+*"			 OPERATOR(TBL_PRE_INC);

[ \t\r]*
[\n]		         {
			   quoted = 0;
			   force_decimal = 0;
			   force_ident = 0;
 			   return yytext[0];
			 }
;.*			 {  }
.			 { 
			   yylval.i = yytext[0];
		           return yytext[0];
			 }
%%

static void
search_pathes(struct source_context *new, char *name)
{
  char tryname[BUFSIZ];
  int i;

  for(i = 0; i < state.path_num; i++) {
    strncpy(tryname, state.paths[i], sizeof(tryname));
    strncat(tryname, COPY_CHAR, sizeof(tryname));
    strncat(tryname, name, sizeof(tryname));
    new->f = fopen(tryname, "rt");
    if(new->f) {
      new->name = strdup(tryname);
      break;
    }
  }

  return; 
}

void open_src(char *name, int isinclude)
{
  extern FILE *yyin;
  struct source_context *new = malloc(sizeof(*new));
  if (state.src)
    state.src->yybuf = YY_CURRENT_BUFFER;

  new->f = fopen(name, "rt");
  if(new->f)
    new->name = strdup(name);
  else if(isinclude && (strchr(name, PATH_CHAR) == 0)) { 
    /* If include file and no PATH_CHAR in name, try searching include 
       path */
    search_pathes(new, name);

    if (new->f == NULL) {
      /* We didn't find a match so check for lower case.  This is mainly for 
         Microchip examples and some includes in which filenames were written
         without regard to case */    
      char *lower_case_name = gp_lower_case(name);
      search_pathes(new, lower_case_name);      
      free(lower_case_name);  

      if (new->f != NULL)
        gpwarning(GPW_UNKNOWN, "found lower case match for include filename");    
    }
  }
  if(new->f)
    new->lst.f = fopen(new->name, "rt");

  yyin = new->f;
  if (new->f == NULL) {
    if (state.src) {
      char complaint[BUFSIZ];

      snprintf(complaint, sizeof(complaint),
               "Unable to open file \"%s\" - %s",
               name,
               strerror(errno));
      state.pass = 2; /* Ensure error actually gets displayed */
      gperror(GPE_UNKNOWN, complaint);
    } else {
      perror(name);
    }
    exit(1);
  }

  if (state.src) {
    yy_switch_to_buffer(yy_create_buffer(yyin, YY_BUF_SIZE));
  }

  if (state.use_absolute_path) {
    new->name = gp_absolute_path(new->name);
  }
  new->type = src_file;
  new->h = NULL;
  new->line_number = 1;
  new->loop_number = 1;
  if (state.debug_info) {
    new->file_symbol = NULL;
  } else {
    new->file_symbol = coff_add_filesym(new->name, isinclude);
  }
  new->prev = state.src;

  state.src = new;
  state.src->fc = add_file(ft_src, new->name); 
  deps_add(new->name);

  if (!isinclude) {
    /* it is the top level file so initialize the lexer */
    quoted = 0;
    force_decimal = 0;
    force_ident = 0;
  }
}

void execute_macro(struct macro_head *h, int is_while)
{
  struct source_context *new = malloc(sizeof(*new));
  yy_size_t macro_src_size = 0;
  char *macro_src;

  assert(state.src != NULL);
  state.src->yybuf = YY_CURRENT_BUFFER;
  /* store the stack so it can be returned when the macro is complete */
  state.src->astack = state.astack;

  macro_src = make_macro_buffer(h);
  macro_src_size = strlen(macro_src) + 2;

  /* create new source_context */
  new->name = strdup(h->src_name);
  if (is_while) {
    new->type = src_while;
  } else {
    new->type = src_macro;
  }
  new->line_number = h->line_number + 1;
  new->loop_number = 1;
  new->file_symbol = h->file_symbol;
  new->f = NULL;
  new->h = h;
  new->lst.m = h->body;
  
  new->prev = state.src;
  state.src = new;

  state.src->fc = add_file(ft_src, new->name); /* scan list for fc */
  yy_scan_buffer(macro_src, macro_src_size);

}

void repeat_while(void)
{
  struct macro_head *h;
  yy_size_t macro_src_size = 0;
  char *macro_src;

  h = state.src->h;

  /* rebuild the macro buffer, because it may have been modified */
  macro_src = make_macro_buffer(h);
  macro_src_size = strlen(macro_src) + 2;

  state.src->line_number = h->line_number + 1;
  state.src->loop_number++;
  state.src->lst.m = h->body;

  yy_delete_buffer(YY_CURRENT_BUFFER);
  yy_scan_buffer(macro_src, macro_src_size);

}

static void push_string(char *str)
{
  struct source_context *new = malloc(sizeof(*new));

  assert(state.src != NULL);
  state.src->yybuf = YY_CURRENT_BUFFER;

  new->name = strdup(state.src->name);
  new->type = src_substitution;
  new->line_number = state.src->line_number;
  new->loop_number = 1;
  new->file_symbol = state.src->file_symbol;
  new->f = NULL;
  new->h = NULL;
  new->lst.f = NULL;
  new->fc = add_file(ft_src, new->name); /* scan list for fc */

  yy_scan_string(str);

  new->prev = state.src;
  state.src = new;
}

void close_file()
{
  struct source_context *old;

  old = state.src;
  state.src = state.src->prev;

  if (old->type == src_file) {
    if (old->f != NULL) {
      fclose(old->f);
    }
    if (old->lst.f != NULL)
      fclose(old->lst.f);
    free(old->name);
    if (!state.debug_info) {
      coff_add_eofsym();
    }
  } else if (old->type == src_macro) {
    state.stTop = pop_symbol_table(state.stTop);
    state.stTopDefines = pop_symbol_table(state.stTopDefines);
    if (state.src->astack != state.astack) {
      gperror(GPE_ILLEGAL_NESTING, NULL);
    }
    assert(state.stTop != NULL);
    assert(state.stTopDefines != NULL);
    free(old->name);
  } else if (old->type == src_while) {
    free(old->name);
  }  
  free(old);
}

void execute_exitm()
{
  struct amode *previous;
  struct amode *old;

  /* The macro is ended early, so return the stack to its previous state */
  previous = state.src->prev->astack;
  while ((state.astack != NULL) &&
         (state.astack != previous)) {
    old = state.astack;
    state.astack = state.astack->prev; 
    free(old);
  }

  close_file();
  if (state.src) {
    yy_delete_buffer(YY_CURRENT_BUFFER);
    yy_switch_to_buffer(state.src->yybuf);
  }

}

/* found end directive, close all files and stop the parser */
static int found_end()
{
  if (state.while_head) {
    state.mac_body = NULL;    
    state.mac_prev = NULL;
    state.while_head = NULL;
    gperror(GPE_EXPECTED, "Expected (ENDW)");
  }

  if (state.astack != NULL) {
    struct amode *old;

    while (state.astack) {
      old = state.astack;
      state.astack = state.astack->prev; 
      free(old);
    }
    gpwarning(GPW_EXPECTED, "Expected (ENDIF)");
  }

  if (state.mac_prev != NULL) {
    gperror(GPW_EXPECTED,"Expected (ENDM)");
  }

  /* close all open files */
  while(state.src) {
    close_file();
  }

  /* make sure the buffer is empty when pass 2 starts */
  if (YY_CURRENT_BUFFER)
    yy_flush_buffer(YY_CURRENT_BUFFER);

  return 1;
}

static int found_eof()
{
  int terminate = 0;

  if (state.src->type == src_while) {
    if (maybe_evaluate(state.src->h->parms)) {
      if (state.src->loop_number > 255) {
        gperror(GPE_BAD_WHILE_LOOP, NULL);
      } else {
        /* repeat the while loop */
        repeat_while();
        return 0;
      }
    }
  }

  close_file();
  if (state.src) {
    /* Just an include file */
    yy_delete_buffer(YY_CURRENT_BUFFER);
    yy_switch_to_buffer(state.src->yybuf);
  } else {
    gperror(GPE_ILLEGAL_COND, 
            "Illegal condition (EOF encountered before END)");
    terminate = found_end();
  }

  return terminate;
}

enum identtype identify(char *text)
{
  enum identtype type;  
  struct symbol *sym;

  if ((sym = get_symbol(state.stTopDefines, text)) != NULL) {
    type = defines;
  } else if ((sym = get_symbol(state.stDirective, text)) != NULL) {
    type = directives;
  } else if ((sym = get_symbol(state.stBuiltin, text)) != NULL) {
    type = opcodes;
  } else if ((sym = get_symbol(state.stGlobal, text)) != NULL) {
    type = globals;
  } else if ((sym = get_symbol(state.stMacros, text)) != NULL) {
    type = macros;
  } else {
    type = unknown_type;
  }

  return type;
}

static char *
check_defines(char *symbol)
{
  struct symbol *sym;
  char *subst = NULL;

  /* If not quoted, check for #define substitution */
  if (!quoted &&
     (sym = get_symbol(state.stTopDefines, yytext)) != NULL) {
    subst = get_symbol_annotation(sym);
    assert(subst != NULL);
    if (strcmp(yytext, subst) == 0) {
      /* check for a bad subsitution */
      subst = NULL;
    }
  }

  return subst;
}


syntax highlighted by Code2HTML, v. 0.9.1