/*
// Filename: lexer.l
// Created by:  drose (16Jan99)
// 
////////////////////////////////////////////////////////////////////
*/

%{
#include "pandabase.h"
#include "lexerDefs.h"
#include "parserDefs.h"
#include "config_egg.h"
#include "parser.h"
#include "indent.h"
#include "pnotify.h"
#include "pmutex.h"

#include <math.h>

extern "C" int eggyywrap(void);  // declared below.

static int yyinput(void);        // declared by flex.


////////////////////////////////////////////////////////////////////
// Static variables
////////////////////////////////////////////////////////////////////

// This mutex protects all of these global variables. 
Mutex egg_lock; 

// We'll increment line_number and col_number as we parse the file, so
// that we can report the position of an error.
static int line_number = 0;
static int col_number = 0;

// current_line holds as much of the current line as will fit.  Its
// only purpose is for printing it out to report an error to the user.
static const int max_error_width = 1024;
static char current_line[max_error_width + 1];

static int error_count = 0;
static int warning_count = 0;

// This is the pointer to the current input stream.
static istream *inp = NULL;

// This is the name of the egg file we're parsing.  We keep it so we
// can print it out for error messages.
static string egg_filename;

// This is the initial token state returned by the lexer.  It allows
// the yacc grammar to start from initial points.
static int initial_token;

////////////////////////////////////////////////////////////////////
// Defining the interface to the lexer.
////////////////////////////////////////////////////////////////////

void
egg_init_lexer(istream &in, const string &filename) {
  inp = &in;
  egg_filename = filename;
  line_number = 0;
  col_number = 0;
  error_count = 0;
  warning_count = 0;
  initial_token = START_EGG;
}

void
egg_start_group_body() {
  /* Set the initial state to begin within a group_body context,
     instead of at the beginning of the egg file. */
  initial_token = START_GROUP_BODY;
}

void
egg_start_texture_body() {
  initial_token = START_TEXTURE_BODY;
}

void
egg_start_primitive_body() {
  initial_token = START_PRIMITIVE_BODY;
}

int
egg_error_count() {
  return error_count;
}

int
egg_warning_count() {
  return warning_count;
}


////////////////////////////////////////////////////////////////////
// Internal support functions.
////////////////////////////////////////////////////////////////////

int
eggyywrap(void) {
  return 1;
}

void
eggyyerror(const string &msg) {
  if (egg_cat.is_error()) {
    ostream &out = egg_cat.error(false);

    out << "\nError";
    if (!egg_filename.empty()) {
      out << " in " << egg_filename;
    }
    out 
      << " at line " << line_number << ", column " << col_number << ":\n"
      << setiosflags(Notify::get_literal_flag())
      << current_line << "\n";
    indent(out, col_number-1) 
      << "^\n" << msg << "\n\n" 
      << resetiosflags(Notify::get_literal_flag()) << flush;
  }
  error_count++;
}

void
eggyyerror(ostringstream &strm) {
  string s = strm.str();
  eggyyerror(s);
}

void
eggyywarning(const string &msg) {
  if (egg_cat.is_warning()) {
    ostream &out = egg_cat.warning(false);

    out << "\nWarning";
    if (!egg_filename.empty()) {
      out << " in " << egg_filename;
    }
    out 
      << " at line " << line_number << ", column " << col_number << ":\n"
      << setiosflags(Notify::get_literal_flag())
      << current_line << "\n";
    indent(out, col_number-1) 
      << "^\n" << msg << "\n\n" 
      << resetiosflags(Notify::get_literal_flag()) << flush;
  }
  warning_count++;
}

void
eggyywarning(ostringstream &strm) {
  string s = strm.str();
  eggyywarning(s);
}

// Now define a function to take input from an istream instead of a
// stdio FILE pointer.  This is flex-specific.
static void
input_chars(char *buffer, int &result, int max_size) {
  nassertv(inp != NULL);
  if (*inp) {
    inp->read(buffer, max_size);
    result = inp->gcount();

    if (line_number == 0) {
      // This is a special case.  If we are reading the very first bit
      // from the stream, copy it into the current_line array.  This
      // is because the \n.* rule below, which fills current_line
      // normally, doesn't catch the first line.
      int length = min(max_error_width, result);
      strncpy(current_line, buffer, length);
      current_line[length] = '\0';
      line_number++;
      col_number = 0;

      // Truncate it at the newline.
      char *end = strchr(current_line, '\n');
      if (end != NULL) {
        *end = '\0';
      }
    }

  } else {
    // End of file or I/O error.
    result = 0;
  }
}
#undef YY_INPUT
#define YY_INPUT(buffer, result, max_size) input_chars(buffer, result, max_size)

// read_char reads and returns a single character, incrementing the
// supplied line and column numbers as appropriate.  A convenience
// function for the scanning functions below.
static int
read_char(int &line, int &col) {
  int c = yyinput();
  if (c == '\n') {
    line++;
    col = 0;
  } else {
    col++;
  }
  return c;
}

// scan_quoted_string reads a string delimited by quotation marks and
// returns it.
static string
scan_quoted_string() {
  string result;

  // We don't touch the current line number and column number during
  // scanning, so that if we detect an error while scanning the string
  // (e.g. an unterminated string), we'll report the error as
  // occurring at the start of the string, not at the end--somewhat
  // more convenient for the user.

  // Instead of adjusting the global line_number and col_number
  // variables, we'll operate on our own local variables for the
  // interim.
  int line = line_number;
  int col = col_number;

  int c;
  c = read_char(line, col);
  while (c != '"' && c != EOF) {
    result += c;
    c = read_char(line, col);
  }

  if (c == EOF) {
    eggyyerror("This quotation mark is unterminated.");
  }

  line_number = line;
  col_number = col;

  return result;
}

// eat_c_comment scans past all characters up until the first */
// encountered.
static void
eat_c_comment() {
  // As above, we'll operate on our own local copies of line_number
  // and col_number within this function.

  int line = line_number;
  int col = col_number;

  int c, last_c;
  
  last_c = '\0';
  c = read_char(line, col);
  while (c != EOF && !(last_c == '*' && c == '/')) {
    if (last_c == '/' && c == '*') {
      ostringstream errmsg;
      errmsg << "This comment contains a nested /* symbol at line "
             << line << ", column " << col-1 << "--possibly unclosed?"
             << ends;
      eggyywarning(errmsg);
    }
    last_c = c;
    c = read_char(line, col);
  }

  if (c == EOF) {
    eggyyerror("This comment marker is unclosed.");
  }

  line_number = line;
  col_number = col;
}


// accept() is called below as each piece is pulled off and
// accepted by the lexer; it increments the current column number.
INLINE void accept() {
  col_number += yyleng;
}

%}

HEX             0x[0-9a-fA-F]*
BINARY          0b[01]*
NUMERIC         ([+-]?(([0-9]+[.]?)|([0-9]*[.][0-9]+))([eE][+-]?[0-9]+)?)

%%

%{
  if (initial_token != 0) {
    int t = initial_token;
    initial_token = 0;
    return t;
  }
%}

\n.* {
  // New line.  Save a copy of the line so we can print it out for the
  // benefit of the user in case we get an error.

  strncpy(current_line, yytext+1, max_error_width);
  current_line[max_error_width] = '\0';
  line_number++;
  col_number=0;

  // Return the whole line to the lexer, except the newline character,
  // which we eat.
  yyless(1);
}

[ \t\r] { 
  // Eat whitespace.
  accept();
}

"//".* { 
  // Eat C++-style comments.
  accept();
}

"/*" {
  // Eat C-style comments.
  accept();
  eat_c_comment(); 
}

[{}] {
  // Send curly braces as themselves.
  accept(); 
  return eggyytext[0];
}



"<BEZIERCURVE>" {
  accept();
  return BEZIERCURVE;
}
"<BFACE>" {
  accept();
  return BFACE;
}
"<BILLBOARD>" {
  accept();
  return BILLBOARD;
}
"<BILLBOARDCENTER>" {
  accept();
  return BILLBOARDCENTER;
}
"<BINORMAL>" {
  accept();
  return BINORMAL;
}
"<BUNDLE>" {
  accept();
  return BUNDLE;
}
"<CHAR*>" {
  accept();
  return SCALAR;
}
"<CLOSED>" {
  accept();
  return CLOSED;
}
"<COLLIDE>" {
  accept();
  return COLLIDE;
}
"<COMMENT>" {
  accept();
  return COMMENT;
}
"<COMPONENT>" {
  accept();
  return COMPONENT;
}
"<COORDINATESYSTEM>" {
  accept();
  return COORDSYSTEM;
}
"<CV>" {
  accept();
  return CV;
}
"<DART>" {
  accept();
  return DART;
}
"<DNORMAL>" {
  accept();
  return DNORMAL;
}
"<DRGBA>" {
  accept();
  return DRGBA;
}
"<DUV>" {
  accept();
  return DUV;
}
"<DXYZ>" {
  accept();
  return DXYZ;
}
"<DCS>" {
  accept();
  return DCS;
}
"<DISTANCE>" {
  accept();
  return DISTANCE;
}
"<DTREF>" {
  accept();
  return DTREF;
}
"<DYNAMICVERTEXPOOL>" {
  accept();
  return DYNAMICVERTEXPOOL;
}
"<FILE>" {
  accept();
  return EXTERNAL_FILE;
}
"<FLT_HEADER>" {
  accept();
  return FLIGHT;
}
"<GROUP>" {
  accept();
  return GROUP;
}
"<HIP>" {
  accept();
  return HIP;
}
"<IN>" {
  accept();
  return INTANGENT;
}
"<JOINT>" {
  accept();
  return JOINT;
}
"<KNOTS>" {
  accept();
  return KNOTS;
}
"<INCLUDE>" {
  accept();
  return INCLUDE;
}
"<INSTANCE>" {
  accept();
  return INSTANCE;
}
"<LINE>" {
  accept();
  return LINE;
}
"<LOOP>" {
  accept();
  return LOOP;
}
"<MATERIAL>" {
  accept();
  return MATERIAL;
}
"<MATRIX3>" {
  accept();
  return MATRIX3;
}
"<MATRIX4>" {
  accept();
  return MATRIX4;
}
"<MODEL>" {
  accept();
  return MODEL;
}
"<MREF>" {
  accept();
  return MREF;
}
"<NORMAL>" {
  accept();
  return NORMAL;
}
"<NURBSCURVE>" {
  accept();
  return NURBSCURVE;
}
"<NURBSSURFACE>" {
  accept();
  return NURBSSURFACE;
}
"<OBJECTTYPE>" {
  accept();
  return OBJECTTYPE;
}
"<ORDER>" {
  accept();
  return ORDER;
}
"<OUTTANGENT>" {
  accept();
  return OUTTANGENT;
}
"<POINTLIGHT>" {
  accept();
  return POINTLIGHT;
}
"<POLYGON>" {
  accept();
  return POLYGON;
}
"<REF>" {
  accept();
  return REF;
}
"<RGBA>" {
  accept();
  return RGBA;
}
"<ROTATE>" {
  accept();
  return ROTATE;
}
"<ROTX>" {
  accept();
  return ROTX;
}
"<ROTY>" {
  accept();
  return ROTY;
}
"<ROTZ>" {
  accept();
  return ROTZ;
}
"<S$ANIM>" {
  accept();
  return SANIM;
}
"<SCALAR>" {
  accept();
  return SCALAR;
}
"<SCALE>" {
  accept();
  return SCALE;
}
"<SEQUENCE>" {
  accept();
  return SEQUENCE;
}
"<SHADING>" {
  accept();
  return SHADING;
}
"<SWITCH>" {
  accept();
  return SWITCH;
}
"<SWITCHCONDITION>" {
  accept();
  return SWITCHCONDITION;
}
"<TABLE>" {
  accept();
  return TABLE;
}
"<V>" {
  accept();
  return TABLE_V;
}
"<TAG>" {
  accept();
  return TAG;
}
"<TANGENT>" {
  accept();
  return TANGENT;
}
"<TEXLIST>" {
  accept();
  return TEXLIST;
}
"<TEXTURE>" {
  accept();
  return TEXTURE;
}
"<TLENGTHS>" {
  accept();
  return TLENGTHS;
}
"<TRANSFORM>" {
  accept();
  return TRANSFORM;
}
"<TRANSLATE>" {
  accept();
  return TRANSLATE;
}
"<TREF>" {
  accept();
  return TREF;
}
"<TRIANGLEFAN>" {
  accept();
  return TRIANGLEFAN;
}
"<TRIANGLESTRIP>" {
  accept();
  return TRIANGLESTRIP;
}
"<TRIM>" {
  accept();
  return TRIM;
}
"<TXT>" {
  accept();
  return TXT;
}
"<U-KNOTS>" {
  accept();
  return UKNOTS;
}
"<U_KNOTS>" {
  accept();
  return UKNOTS;
}
"<UV>" {
  accept();
  return UV;
}
"<V-KNOTS>" {
  accept();
  return VKNOTS;
}
"<V_KNOTS>" {
  accept();
  return VKNOTS;
}
"<VERTEX>" {
  accept();
  return VERTEX;
}
"<VERTEXANIM>" {
  accept();
  return VERTEXANIM;
}
"<VERTEXPOOL>" {
  accept();
  return VERTEXPOOL;
}
"<VERTEXREF>" {
  accept();
  return VERTEXREF;
}
"<XFM$ANIM>" {
  accept();
  return XFMANIM;
}
"<XFM$ANIM_S$>" {
  accept();
  return XFMSANIM;
}



{NUMERIC} { 
  // An integer or floating-point number.
  accept(); 
  eggyylval._number = atof(eggyytext); 
  eggyylval._string = yytext;
  return EGG_NUMBER; 
}

{HEX} {
  // A hexadecimal integer number.
  accept(); 
  eggyylval._ulong = strtoul(yytext+2, NULL, 16);
  eggyylval._string = yytext;
  return EGG_ULONG; 
}

{BINARY} {
  // A binary integer number.
  accept(); 
  eggyylval._ulong = strtoul(yytext+2, NULL, 2);
  eggyylval._string = yytext;
  return EGG_ULONG; 
}

"nan"{HEX} {
  // not-a-number.  These sometimes show up in egg files accidentally.
  accept(); 
  memset(&eggyylval._number, 0, sizeof(eggyylval._number));
  *(unsigned long *)&eggyylval._number = strtoul(yytext+3, NULL, 0);
  eggyylval._string = yytext;
  return EGG_NUMBER;
}

"inf" { 
  // infinity.  As above.
  accept(); 
  eggyylval._number = HUGE_VAL;
  eggyylval._string = yytext;
  return EGG_NUMBER; 
}

"-inf" {
  // minus infinity.  As above.
  accept(); 
  eggyylval._number = -HUGE_VAL;
  eggyylval._string = yytext;
  return EGG_NUMBER; 
}


["] {
  // Quoted string.
  accept();
  eggyylval._string = scan_quoted_string();
  return EGG_STRING;
}

[^ \t\n\r{}"]+ { 
  // Unquoted string.
  accept();
  eggyylval._string = yytext;
  return EGG_STRING;
}
