/*
// Filename: lexer.l
// Created by:  drose (08Nov99)
// 
////////////////////////////////////////////////////////////////////
*/

%{
#include "stitchLexerDefs.h"
#include "stitchParserDefs.h"
#include "stitchParser.h"

#include <indent.h>
#include <string_utils.h>

#include <assert.h>
#include <math.h>

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

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


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

// 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 stitch file we're parsing.  We keep it so we
// can print it out for error messages.
static string stitch_filename;

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

void
stitch_init_lexer(istream &in, const string &filename) {
  inp = &in;
  stitch_filename = filename;
  line_number = 0;
  col_number = 0;
  error_count = 0;
  warning_count = 0;
}

int
stitch_error_count() {
  return error_count;
}

int
stitch_warning_count() {
  return warning_count;
}


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

int
stitchyywrap(void) {
  return 1;
}

void
stitchyyerror(const string &msg) {
  nout << "\nError";
  if (!stitch_filename.empty()) {
    nout << " in " << stitch_filename;
  }
  nout 
    << " at line " << line_number << ", column " << col_number << ":\n"
    << current_line << "\n";
  indent(nout, col_number-1) 
    << "^\n" << msg << "\n\n" << flush;
  error_count++;
}

void
stitchyyerror(ostringstream &strm) {
  stitchyyerror(strm.str());
}

void
stitchyywarning(const string &msg) {
  nout 
    << "\nWarning at line " << line_number << ", column " << col_number << ":\n"
    << current_line << "\n";
  indent(nout, col_number-1) 
    << "^\n" << msg << "\n\n" << flush;
  warning_count++;
}

void
stitchyywarning(ostringstream &strm) {
  stitchyywarning(strm);
}

// 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) {
  assert(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.
      strncpy(current_line, yytext, max_error_width);
      current_line[max_error_width] = '\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) {
    stitchyyerror("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?";
      stitchyywarning(errmsg);
    }
    last_c = c;
    c = read_char(line, col);
  }

  if (c == EOF) {
    stitchyyerror("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;
}

%}

NUMERIC         ([+-]?(([0-9]+[.]?)|([0-9]*[.][0-9]+))([eE][+-]?[0-9]+)?)

%%

\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] { 
  // Eat whitespace.
  accept();
}

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

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



{NUMERIC} { 
  // An integer or floating-point number.
  accept(); 
  stitchyylval.number = atof(stitchyytext); 
  stitchyylval.str = yytext;
  return NUMBER; 
}

{NUMERIC}['] { 
  // An integer or floating-point number followed by feet symbol.
  accept(); 
  stitchyylval.number = atof(stitchyytext); 
  stitchyylval.str = yytext;
  return NUMBER_FEET; 
}

{NUMERIC}["] { 
  // An integer or floating-point number followed by inches symbol.
  accept(); 
  stitchyylval.number = atof(stitchyytext); 
  stitchyylval.str = yytext;
  return NUMBER_INCHES; 
}

["] {
  // Quoted string.
  accept();
  stitchyylval.str = scan_quoted_string();
  return STRING;
}

[a-zA-Z][a-zA-Z0-9_-]* {
  // Identifier or keyword.
  accept();
  string str = yytext;
  stitchyylval.str = str;

  if (cmp_nocase_uh(str, "define") == 0) {
    return KW_DEFINE;
  } else if (cmp_nocase_uh(str, "lens") == 0) {
    return KW_LENS;
  } else if (cmp_nocase_uh(str, "input_image") == 0) {
    return KW_INPUT_IMAGE;
  } else if (cmp_nocase_uh(str, "output_image") == 0) {
    return KW_OUTPUT_IMAGE;
  } else if (cmp_nocase_uh(str, "eyepoint") == 0) {
    return KW_EYEPOINT;
  } else if (cmp_nocase_uh(str, "perspective") == 0) {
    return KW_PERSPECTIVE;
  } else if (cmp_nocase_uh(str, "fisheye") == 0) {
    return KW_FISHEYE;
  } else if (cmp_nocase_uh(str, "cylindrical") == 0) {
    return KW_CYLINDRICAL;
  } else if (cmp_nocase_uh(str, "psphere") == 0) {
    return KW_PSPHERE;
  } else if (cmp_nocase_uh(str, "focal_length") == 0) {
    return KW_FOCAL_LENGTH;
  } else if (cmp_nocase_uh(str, "fov") == 0) {
    return KW_FOV;
  } else if (cmp_nocase_uh(str, "singularity_tolerance") == 0) {
    return KW_SINGULARITY_TOLERANCE;
  } else if (cmp_nocase_uh(str, "resolution") == 0) {
    return KW_RESOLUTION;
  } else if (cmp_nocase_uh(str, "filename") == 0) {
    return KW_FILENAME;
  } else if (cmp_nocase_uh(str, "fade") == 0) {
    return KW_FADE;
  } else if (cmp_nocase_uh(str, "point") == 0) {
    return KW_POINT;
  } else if (cmp_nocase_uh(str, "show_points") == 0) {
    return KW_SHOW_POINTS;
  } else if (cmp_nocase_uh(str, "image_size") == 0) {
    return KW_IMAGE_SIZE;
  } else if (cmp_nocase_uh(str, "film_size") == 0) {
    return KW_FILM_SIZE;
  } else if (cmp_nocase_uh(str, "film_offset") == 0) {
    return KW_FILM_OFFSET;
  } else if (cmp_nocase_uh(str, "grid") == 0) {
    return KW_GRID;
  } else if (cmp_nocase_uh(str, "untextured_color") == 0) {
    return KW_UNTEXTURED_COLOR;
  } else if (cmp_nocase_uh(str, "hpr") == 0) {
    return KW_HPR;
  } else if (cmp_nocase_uh(str, "pos") == 0) {
    return KW_POS;
  } else if (cmp_nocase_uh(str, "radius") == 0) {
    return KW_RADIUS;
  } else if (cmp_nocase_uh(str, "angle") == 0) {
    return KW_ANGLE;
  } else if (cmp_nocase_uh(str, "height") == 0) {
    return KW_HEIGHT;
  } else if (cmp_nocase_uh(str, "layers") == 0) {
    return KW_LAYERS;
  } else if (cmp_nocase_uh(str, "stitch") == 0) {
    return KW_STITCH;
  } else if (cmp_nocase_uh(str, "screen") == 0) {
    return KW_SCREEN;
  } else if (cmp_nocase_uh(str, "points") == 0) {
    return KW_POINTS;
  } else if (cmp_nocase_uh(str, "using") == 0) {
    return KW_USING;
  } else if (cmp_nocase_uh(str, "in") == 0) {
    return KW_IN;
  } else if (cmp_nocase_uh(str, "mm") == 0) {
    return KW_MM;
  } else if (cmp_nocase_uh(str, "cm") == 0) {
    return KW_CM;
  } else if (cmp_nocase_uh(str, "ft") == 0) {
    return KW_FT;
  } else if (cmp_nocase_uh(str, "p") == 0) {
    return KW_P;
  }

  return IDENTIFIER;
}

. {
  // Send any other character as itself.
  accept(); 
  return stitchyytext[0];
}
